X,y "package jadx.gui.settings.ui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ScrollPaneConstants; import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import say.swing.JFontChooser; import jadx.api.CommentsLevel; import jadx.api.DecompilationMode; import jadx.api.JadxArgs; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; import jadx.api.JadxDecompiler; import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.api.args.IntegerFormat; import jadx.api.args.ResourceNameSource; import jadx.api.plugins.events.JadxEvents; import jadx.api.plugins.gui.ISettingsGroup; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsAdapter; import jadx.gui.settings.LineNumbersMode; import jadx.gui.settings.ui.cache.CacheSettingsGroup; import jadx.gui.settings.ui.plugins.PluginSettings; import jadx.gui.settings.ui.shortcut.ShortcutsSettingsGroup; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; import jadx.gui.utils.LafManager; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; import jadx.gui.utils.ui.ActionHandler; import jadx.gui.utils.ui.DocumentUpdateListener; public class JadxSettingsWindow extends JDialog { private static final long serialVersionUID = -1804570470377354148L; private static final Logger LOG = LoggerFactory.getLogger(JadxSettingsWindow.class); private final transient MainWindow mainWindow; private final transient JadxSettings settings; private final transient String startSettings; private final transient String startSettingsHash; private final transient LangLocale prevLang; private transient boolean needReload = false; private transient SettingsTree tree; public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) { this.mainWindow = mainWindow; this.settings = settings; this.startSettings = JadxSettingsAdapter.makeString(settings); this.startSettingsHash = calcSettingsHash(); this.prevLang = settings.getLangLocale(); initUI(); setTitle(NLS.str(""preferences.title"")); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setModalityType(ModalityType.APPLICATION_MODAL); pack(); UiUtils.setWindowIcons(this); setLocationRelativeTo(null); if (!mainWindow.getSettings().loadWindowPos(this)) { setSize(700, 800); } mainWindow.events().addListener(JadxEvents.RELOAD_SETTINGS_WINDOW, r -> UiUtils.uiRun(this::reloadUI)); mainWindow.events().addListener(JadxEvents.RELOAD_PROJECT, r -> UiUtils.uiRun(this::reloadUI)); } private void reloadUI() { int[] selection = tree.getSelectionRows(); getContentPane().removeAll(); initUI(); // wait for other events to process UiUtils.uiRun(() -> { tree.setSelectionRows(selection); SwingUtilities.updateComponentTreeUI(this); }); } private void initUI() { JPanel wrapGroupPanel = new JPanel(new BorderLayout(10, 10)); List groups = new ArrayList<>(); groups.add(makeDecompilationGroup()); groups.add(makeDeobfuscationGroup()); groups.add(makeRenameGroup()); groups.add(new CacheSettingsGroup(this)); groups.add(makeAppearanceGroup()); groups.add(new ShortcutsSettingsGroup(this, settings)); groups.add(makeSearchResGroup()); groups.add(makeProjectGroup()); groups.add(new PluginSettings(mainWindow, settings).build()); groups.add(makeOtherGroup()); tree = new SettingsTree(); tree.init(wrapGroupPanel, groups); JScrollPane leftPane = new JScrollPane(tree); leftPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 3, 3)); JScrollPane rightPane = new JScrollPane(wrapGroupPanel); rightPane.getVerticalScrollBar().setUnitIncrement(16); rightPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); rightPane.setBorder(BorderFactory.createEmptyBorder(10, 3, 3, 10)); JSplitPane splitPane = new JSplitPane(); splitPane.setResizeWeight(0.2); splitPane.setLeftComponent(leftPane); splitPane.setRightComponent(rightPane); Container contentPane = getContentPane(); contentPane.add(splitPane, BorderLayout.CENTER); contentPane.add(buildButtonsPane(), BorderLayout.PAGE_END); KeyStroke strokeEsc = KeyStroke.getKeyStroke(""ESCAPE""); InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); inputMap.put(strokeEsc, ""ESCAPE""); getRootPane().getActionMap().put(""ESCAPE"", new ActionHandler(this::cancel)); } private JPanel buildButtonsPane() { JButton saveBtn = new JButton(NLS.str(""preferences.save"")); saveBtn.addActionListener(event -> save()); JButton cancelButton = new JButton(NLS.str(""preferences.cancel"")); cancelButton.addActionListener(event -> cancel()); JButton resetBtn = new JButton(NLS.str(""preferences.reset"")); resetBtn.addActionListener(event -> reset()); JButton copyBtn = new JButton(NLS.str(""preferences.copy"")); copyBtn.addActionListener(event -> copySettings()); JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); buttonPane.add(resetBtn); buttonPane.add(copyBtn); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(saveBtn); buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); buttonPane.add(cancelButton); getRootPane().setDefaultButton(saveBtn); return buttonPane; } private static void enableComponents(Container container, boolean enable) { for (Component component : container.getComponents()) { if (component instanceof Container) { enableComponents((Container) component, enable); } component.setEnabled(enable); } } private SettingsGroup makeDeobfuscationGroup() { JCheckBox [MASK] = new JCheckBox(); [MASK] .setSelected(settings.isDeobfuscationOn()); [MASK] .addItemListener(e -> { settings.setDeobfuscationOn(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); SpinnerNumberModel minLenModel = new SpinnerNumberModel(settings.getDeobfuscationMinLength(), 0, Integer.MAX_VALUE, 1); JSpinner minLenSpinner = new JSpinner(minLenModel); minLenSpinner.addChangeListener(e -> { settings.setDeobfuscationMinLength((Integer) minLenSpinner.getValue()); needReload(); }); SpinnerNumberModel maxLenModel = new SpinnerNumberModel(settings.getDeobfuscationMaxLength(), 0, Integer.MAX_VALUE, 1); JSpinner maxLenSpinner = new JSpinner(maxLenModel); maxLenSpinner.addChangeListener(e -> { settings.setDeobfuscationMaxLength((Integer) maxLenSpinner.getValue()); needReload(); }); JComboBox resNamesSource = new JComboBox<>(ResourceNameSource.values()); resNamesSource.setSelectedItem(settings.getResourceNameSource()); resNamesSource.addActionListener(e -> { settings.setResourceNameSource((ResourceNameSource) resNamesSource.getSelectedItem()); needReload(); }); JComboBox generatedRenamesMappingFileModeCB = new JComboBox<>(GeneratedRenamesMappingFileMode.values()); generatedRenamesMappingFileModeCB.setSelectedItem(settings.getGeneratedRenamesMappingFileMode()); generatedRenamesMappingFileModeCB.addActionListener(e -> { GeneratedRenamesMappingFileMode newValue = (GeneratedRenamesMappingFileMode) generatedRenamesMappingFileModeCB.getSelectedItem(); if (newValue != settings.getGeneratedRenamesMappingFileMode()) { settings.setGeneratedRenamesMappingFileMode(newValue); needReload(); } }); SettingsGroup deobfGroup = new SettingsGroup(NLS.str(""preferences.deobfuscation"")); deobfGroup.addRow(NLS.str(""preferences.deobfuscation_on""), [MASK] ); deobfGroup.addRow(NLS.str(""preferences.deobfuscation_min_len""), minLenSpinner); deobfGroup.addRow(NLS.str(""preferences.deobfuscation_max_len""), maxLenSpinner); deobfGroup.addRow(NLS.str(""preferences.deobfuscation_res_name_source""), resNamesSource); deobfGroup.addRow(NLS.str(""preferences.generated_renames_mapping_file_mode""), generatedRenamesMappingFileModeCB); deobfGroup.end(); Collection connectedComponents = Arrays.asList(minLenSpinner, maxLenSpinner); [MASK] .addItemListener(e -> enableComponentList(connectedComponents, e.getStateChange() == ItemEvent.SELECTED)); enableComponentList(connectedComponents, settings.isDeobfuscationOn()); return deobfGroup; } private SettingsGroup makeRenameGroup() { JCheckBox renameCaseSensitive = new JCheckBox(); renameCaseSensitive.setSelected(settings.isRenameCaseSensitive()); renameCaseSensitive.addItemListener(e -> { settings.updateRenameFlag(JadxArgs.RenameEnum.CASE, e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox renameValid = new JCheckBox(); renameValid.setSelected(settings.isRenameValid()); renameValid.addItemListener(e -> { settings.updateRenameFlag(JadxArgs.RenameEnum.VALID, e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox renamePrintable = new JCheckBox(); renamePrintable.setSelected(settings.isRenamePrintable()); renamePrintable.addItemListener(e -> { settings.updateRenameFlag(JadxArgs.RenameEnum.PRINTABLE, e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox deobfSourceAlias = new JCheckBox(); deobfSourceAlias.setSelected(settings.isDeobfuscationUseSourceNameAsAlias()); deobfSourceAlias.addItemListener(e -> { settings.setDeobfuscationUseSourceNameAsAlias(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); SettingsGroup group = new SettingsGroup(NLS.str(""preferences.rename"")); group.addRow(NLS.str(""preferences.rename_case""), renameCaseSensitive); group.addRow(NLS.str(""preferences.rename_valid""), renameValid); group.addRow(NLS.str(""preferences.rename_printable""), renamePrintable); group.addRow(NLS.str(""preferences.deobfuscation_source_alias""), deobfSourceAlias); return group; } private void enableComponentList(Collection connectedComponents, boolean enabled) { connectedComponents.forEach(comp -> comp.setEnabled(enabled)); } private SettingsGroup makeProjectGroup() { JCheckBox autoSave = new JCheckBox(); autoSave.setSelected(settings.isAutoSaveProject()); autoSave.addItemListener(e -> settings.setAutoSaveProject(e.getStateChange() == ItemEvent.SELECTED)); SettingsGroup group = new SettingsGroup(NLS.str(""preferences.project"")); group.addRow(NLS.str(""preferences.autoSave""), autoSave); return group; } private SettingsGroup makeAppearanceGroup() { JComboBox languageCbx = new JComboBox<>(NLS.getLangLocales()); for (LangLocale locale : NLS.getLangLocales()) { if (locale.equals(settings.getLangLocale())) { languageCbx.setSelectedItem(locale); break; } } languageCbx.addActionListener(e -> settings.setLangLocale((LangLocale) languageCbx.getSelectedItem())); JButton fontBtn = new JButton(NLS.str(""preferences.select_font"")); JButton smaliFontBtn = new JButton(NLS.str(""preferences.select_smali_font"")); EditorTheme[] editorThemes = EditorTheme.getAllThemes(); JComboBox themesCbx = new JComboBox<>(editorThemes); for (EditorTheme theme : editorThemes) { if (theme.getPath().equals(settings.getEditorThemePath())) { themesCbx.setSelectedItem(theme); break; } } themesCbx.addActionListener(e -> { int i = themesCbx.getSelectedIndex(); EditorTheme editorTheme = editorThemes[i]; settings.setEditorThemePath(editorTheme.getPath()); mainWindow.loadSettings(); }); JComboBox lafCbx = new JComboBox<>(LafManager.getThemes()); lafCbx.setSelectedItem(settings.getLafTheme()); lafCbx.addActionListener(e -> { settings.setLafTheme((String) lafCbx.getSelectedItem()); mainWindow.loadSettings(); }); SettingsGroup group = new SettingsGroup(NLS.str(""preferences.appearance"")); group.addRow(NLS.str(""preferences.language""), languageCbx); group.addRow(NLS.str(""preferences.laf_theme""), lafCbx); group.addRow(NLS.str(""preferences.theme""), themesCbx); JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn); JLabel smaliFontLabel = group.addRow(getSmaliFontLabelStr(), smaliFontBtn); fontBtn.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { JFontChooser fontChooser = new JFontChooser(); fontChooser.setSelectedFont(settings.getFont()); int result = fontChooser.showDialog(JadxSettingsWindow.this); if (result == JFontChooser.OK_OPTION) { Font font = fontChooser.getSelectedFont(); LOG.debug(""Selected Font: {}"", font); settings.setFont(font); mainWindow.loadSettings(); fontLabel.setText(getFontLabelStr()); } } }); smaliFontBtn.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { JFontChooser fontChooser = new JPreferredFontChooser(); fontChooser.setSelectedFont(settings.getSmaliFont()); int result = fontChooser.showDialog(JadxSettingsWindow.this); if (result == JFontChooser.OK_OPTION) { Font font = fontChooser.getSelectedFont(); LOG.debug(""Selected Font: {} for smali"", font); settings.setSmaliFont(font); mainWindow.loadSettings(); smaliFontLabel.setText(getSmaliFontLabelStr()); } } }); return group; } private String getFontLabelStr() { Font font = settings.getFont(); String fontStyleName = FontUtils.convertFontStyleToString(font.getStyle()); return NLS.str(""preferences.font"") + "": "" + font.getFontName() + ' ' + fontStyleName + ' ' + font.getSize(); } private String getSmaliFontLabelStr() { Font font = settings.getSmaliFont(); String fontStyleName = FontUtils.convertFontStyleToString(font.getStyle()); return NLS.str(""preferences.smali_font"") + "": "" + font.getFontName() + ' ' + fontStyleName + ' ' + font.getSize(); } private SettingsGroup makeDecompilationGroup() { JCheckBox useDx = new JCheckBox(); useDx.setSelected(settings.isUseDx()); useDx.addItemListener(e -> { settings.setUseDx(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JComboBox decompilationModeComboBox = new JComboBox<>(DecompilationMode.values()); decompilationModeComboBox.setSelectedItem(settings.getDecompilationMode()); decompilationModeComboBox.addActionListener(e -> { settings.setDecompilationMode((DecompilationMode) decompilationModeComboBox.getSelectedItem()); needReload(); }); JCheckBox showInconsistentCode = new JCheckBox(); showInconsistentCode.setSelected(settings.isShowInconsistentCode()); showInconsistentCode.addItemListener(e -> { settings.setShowInconsistentCode(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox resourceDecode = new JCheckBox(); resourceDecode.setSelected(settings.isSkipResources()); resourceDecode.addItemListener(e -> { settings.setSkipResources(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); // fix for #1331 int threadsCountValue = settings.getThreadsCount(); int threadsCountMax = Math.max(2, Math.max(threadsCountValue, Runtime.getRuntime().availableProcessors() * 2)); SpinnerNumberModel spinnerModel = new SpinnerNumberModel(threadsCountValue, 1, threadsCountMax, 1); JSpinner threadsCount = new JSpinner(spinnerModel); threadsCount.addChangeListener(e -> { settings.setThreadsCount((Integer) threadsCount.getValue()); needReload(); }); JButton editExcludedPackages = new JButton(NLS.str(""preferences.excludedPackages.button"")); editExcludedPackages.addActionListener(event -> { String oldExcludedPackages = settings.getExcludedPackages(); String result = JOptionPane.showInputDialog(this, NLS.str(""preferences.excludedPackages.editDialog""), settings.getExcludedPackages()); if (result != null) { settings.setExcludedPackages(result); if (!oldExcludedPackages.equals(result)) { needReload(); } } }); JCheckBox autoStartJobs = new JCheckBox(); autoStartJobs.setSelected(settings.isAutoStartJobs()); autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED)); JCheckBox escapeUnicode = new JCheckBox(); escapeUnicode.setSelected(settings.isEscapeUnicode()); escapeUnicode.addItemListener(e -> { settings.setEscapeUnicode(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox replaceConsts = new JCheckBox(); replaceConsts.setSelected(settings.isReplaceConsts()); replaceConsts.addItemListener(e -> { settings.setReplaceConsts(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox respectBytecodeAccessModifiers = new JCheckBox(); respectBytecodeAccessModifiers.setSelected(settings.isRespectBytecodeAccessModifiers()); respectBytecodeAccessModifiers.addItemListener(e -> { settings.setRespectBytecodeAccessModifiers(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox useImports = new JCheckBox(); useImports.setSelected(settings.isUseImports()); useImports.addItemListener(e -> { settings.setUseImports(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox useDebugInfo = new JCheckBox(); useDebugInfo.setSelected(settings.isDebugInfo()); useDebugInfo.addItemListener(e -> { settings.setDebugInfo(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox inlineAnonymous = new JCheckBox(); inlineAnonymous.setSelected(settings.isInlineAnonymousClasses()); inlineAnonymous.addItemListener(e -> { settings.setInlineAnonymousClasses(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox inlineMethods = new JCheckBox(); inlineMethods.setSelected(settings.isInlineMethods()); inlineMethods.addItemListener(e -> { settings.setInlineMethods(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox inlineKotlinLambdas = new JCheckBox(); inlineKotlinLambdas.setSelected(settings.isAllowInlineKotlinLambda()); inlineKotlinLambdas.addItemListener(e -> { settings.setAllowInlineKotlinLambda(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox moveInnerClasses = new JCheckBox(); moveInnerClasses.setSelected(settings.isMoveInnerClasses()); moveInnerClasses.addItemListener(e -> { settings.setMoveInnerClasses(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox extractFinally = new JCheckBox(); extractFinally.setSelected(settings.isExtractFinally()); extractFinally.addItemListener(e -> { settings.setExtractFinally(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox fsCaseSensitive = new JCheckBox(); fsCaseSensitive.setSelected(settings.isFsCaseSensitive()); fsCaseSensitive.addItemListener(e -> { settings.setFsCaseSensitive(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JComboBox kotlinRenameVars = new JComboBox<>(UseKotlinMethodsForVarNames.values()); kotlinRenameVars.setSelectedItem(settings.getUseKotlinMethodsForVarNames()); kotlinRenameVars.addActionListener(e -> { settings.setUseKotlinMethodsForVarNames((UseKotlinMethodsForVarNames) kotlinRenameVars.getSelectedItem()); needReload(); }); JComboBox commentsLevel = new JComboBox<>(CommentsLevel.values()); commentsLevel.setSelectedItem(settings.getCommentsLevel()); commentsLevel.addActionListener(e -> { settings.setCommentsLevel((CommentsLevel) commentsLevel.getSelectedItem()); needReload(); }); JComboBox integerFormat = new JComboBox<>(IntegerFormat.values()); integerFormat.setSelectedItem(settings.getIntegerFormat()); integerFormat.addActionListener(e -> { settings.setIntegerFormat((IntegerFormat) integerFormat.getSelectedItem()); needReload(); }); SettingsGroup other = new SettingsGroup(NLS.str(""preferences.decompile"")); other.addRow(NLS.str(""preferences.threads""), threadsCount); other.addRow(NLS.str(""preferences.excludedPackages""), NLS.str(""preferences.excludedPackages.tooltip""), editExcludedPackages); other.addRow(NLS.str(""preferences.start_jobs""), autoStartJobs); other.addRow(NLS.str(""preferences.decompilationMode""), decompilationModeComboBox); other.addRow(NLS.str(""preferences.showInconsistentCode""), showInconsistentCode); other.addRow(NLS.str(""preferences.escapeUnicode""), escapeUnicode); other.addRow(NLS.str(""preferences.replaceConsts""), replaceConsts); other.addRow(NLS.str(""preferences.respectBytecodeAccessModifiers""), respectBytecodeAccessModifiers); other.addRow(NLS.str(""preferences.useImports""), useImports); other.addRow(NLS.str(""preferences.useDebugInfo""), useDebugInfo); other.addRow(NLS.str(""preferences.inlineAnonymous""), inlineAnonymous); other.addRow(NLS.str(""preferences.inlineMethods""), inlineMethods); other.addRow(NLS.str(""preferences.inlineKotlinLambdas""), inlineKotlinLambdas); other.addRow(NLS.str(""preferences.moveInnerClasses""), moveInnerClasses); other.addRow(NLS.str(""preferences.extractFinally""), extractFinally); other.addRow(NLS.str(""preferences.fsCaseSensitive""), fsCaseSensitive); other.addRow(NLS.str(""preferences.useDx""), useDx); other.addRow(NLS.str(""preferences.skipResourcesDecode""), resourceDecode); other.addRow(NLS.str(""preferences.useKotlinMethodsForVarNames""), kotlinRenameVars); other.addRow(NLS.str(""preferences.commentsLevel""), commentsLevel); other.addRow(NLS.str(""preferences.integerFormat""), integerFormat); return other; } private SettingsGroup makeOtherGroup() { JComboBox lineNumbersMode = new JComboBox<>(LineNumbersMode.values()); lineNumbersMode.setSelectedItem(settings.getLineNumbersMode()); lineNumbersMode.addActionListener(e -> { settings.setLineNumbersMode((LineNumbersMode) lineNumbersMode.getSelectedItem()); mainWindow.loadSettings(); }); JCheckBox jumpOnDoubleClick = new JCheckBox(); jumpOnDoubleClick.setSelected(settings.isJumpOnDoubleClick()); jumpOnDoubleClick.addItemListener(e -> settings.setJumpOnDoubleClick(e.getStateChange() == ItemEvent.SELECTED)); JCheckBox useAltFileDialog = new JCheckBox(); useAltFileDialog.setSelected(settings.isUseAlternativeFileDialog()); useAltFileDialog.addItemListener(e -> settings.setUseAlternativeFileDialog(e.getStateChange() == ItemEvent.SELECTED)); JCheckBox update = new JCheckBox(); update.setSelected(settings.isCheckForUpdates()); update.addItemListener(e -> settings.setCheckForUpdates(e.getStateChange() == ItemEvent.SELECTED)); JCheckBox cfg = new JCheckBox(); cfg.setSelected(settings.isCfgOutput()); cfg.addItemListener(e -> { settings.setCfgOutput(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); JCheckBox rawCfg = new JCheckBox(); rawCfg.setSelected(settings.isRawCfgOutput()); rawCfg.addItemListener(e -> { settings.setRawCfgOutput(e.getStateChange() == ItemEvent.SELECTED); needReload(); }); SettingsGroup group = new SettingsGroup(NLS.str(""preferences.other"")); group.addRow(NLS.str(""preferences.lineNumbersMode""), lineNumbersMode); group.addRow(NLS.str(""preferences.jumpOnDoubleClick""), jumpOnDoubleClick); group.addRow(NLS.str(""preferences.useAlternativeFileDialog""), useAltFileDialog); group.addRow(NLS.str(""preferences.check_for_updates""), update); group.addRow(NLS.str(""preferences.cfg""), cfg); group.addRow(NLS.str(""preferences.raw_cfg""), rawCfg); return group; } private SettingsGroup makeSearchResGroup() { JSpinner resultsPerPage = new JSpinner( new SpinnerNumberModel(settings.getSearchResultsPerPage(), 0, Integer.MAX_VALUE, 1)); resultsPerPage.addChangeListener(ev -> settings.setSearchResultsPerPage((Integer) resultsPerPage.getValue())); JSpinner sizeLimit = new JSpinner( new SpinnerNumberModel(settings.getSrhResourceSkipSize(), 0, Integer.MAX_VALUE, 1)); sizeLimit.addChangeListener(ev -> settings.setSrhResourceSkipSize((Integer) sizeLimit.getValue())); JTextField fileExtField = new JTextField(); fileExtField.getDocument().addDocumentListener(new DocumentUpdateListener((ev) -> { String ext = fileExtField.getText(); settings.setSrhResourceFileExt(ext); })); fileExtField.setText(settings.getSrhResourceFileExt()); SettingsGroup searchGroup = new SettingsGroup(NLS.str(""preferences.search_group_title"")); searchGroup.addRow(NLS.str(""preferences.search_results_per_page""), resultsPerPage); searchGroup.addRow(NLS.str(""preferences.res_skip_file""), sizeLimit); searchGroup.addRow(NLS.str(""preferences.res_file_ext""), fileExtField); return searchGroup; } private void save() { settings.sync(); enableComponents(this, false); SwingUtilities.invokeLater(() -> { if (shouldReload()) { mainWindow.getShortcutsController().loadSettings(); mainWindow.reopen(); } if (!settings.getLangLocale().equals(prevLang)) { JOptionPane.showMessageDialog( this, NLS.str(""msg.language_changed"", settings.getLangLocale()), NLS.str(""msg.language_changed_title"", settings.getLangLocale()), JOptionPane.INFORMATION_MESSAGE); } dispose(); }); } private void cancel() { JadxSettingsAdapter.fill(settings, startSettings); mainWindow.loadSettings(); dispose(); } private void reset() { int res = JOptionPane.showConfirmDialog( JadxSettingsWindow.this, NLS.str(""preferences.reset_message""), NLS.str(""preferences.reset_title""), JOptionPane.YES_NO_OPTION); if (res == JOptionPane.YES_OPTION) { String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault()); JadxSettingsAdapter.fill(settings, defaults); mainWindow.loadSettings(); needReload(); getContentPane().removeAll(); initUI(); pack(); repaint(); } } private void copySettings() { JsonObject settingsJson = JadxSettingsAdapter.makeJsonObject(this.settings); // remove irrelevant preferences settingsJson.remove(""windowPos""); settingsJson.remove(""mainWindowExtendedState""); settingsJson.remove(""lastSaveProjectPath""); settingsJson.remove(""lastOpenFilePath""); settingsJson.remove(""lastSaveFilePath""); settingsJson.remove(""recentProjects""); String settingsText = new GsonBuilder().setPrettyPrinting().create().toJson(settingsJson); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(settingsText); clipboard.setContents(selection, selection); JOptionPane.showMessageDialog( JadxSettingsWindow.this, NLS.str(""preferences.copy_message"")); } public void needReload() { needReload = true; } private boolean shouldReload() { return needReload || !startSettingsHash.equals(calcSettingsHash()); } @SuppressWarnings(""resource"") private String calcSettingsHash() { JadxDecompiler decompiler = mainWindow.getWrapper().getCurrentDecompiler().orElse(null); return settings.toJadxArgs().makeCodeArgsHash(decompiler); } public MainWindow getMainWindow() { return mainWindow; } @Override public void dispose() { settings.saveWindowPos(this); super.dispose(); } } ","deobfOn " "// Copyright 2018 The Bazel Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.exec; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.devtools.build.lib.actions.ActionContext; import com.google.devtools.build.lib.util.AbruptExitException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Unit tests for {@link ModuleActionContextRegistry}. */ @RunWith(JUnit4.class) public class ModuleActionContextRegistryTest { @Test public void testRegistration() throws Exception { AC2 context = new AC2(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder().register(IT1.class, context).build(); assertThat(contextRegistry.getContext(IT1.class)).isEqualTo(context); } @Test public void testDoubleRegistration() throws Exception { AC2 context = new AC2(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder() .register(IT1.class, context) .register(IT1.class, context) .build(); assertThat(contextRegistry.getContext(IT1.class)).isEqualTo(context); } @Test public void testLastRegisteredHasPriority() throws Exception { AC2 context1 = new AC2(); AC2 context2 = new AC2(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder() .register(IT1.class, context1) .register(IT1.class, context2) .build(); assertThat(contextRegistry.getContext(IT1.class)).isEqualTo(context2); } @Test public void testSelfIdentifyingType() throws Exception { AC1 context = new AC1(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder().register(AC1.class, context).build(); assertThat(contextRegistry.getContext(AC1.class)).isEqualTo(context); } @Test public void testIdentifierFilter() throws Exception { AC2 general = new AC2(); AC2 specific = new AC2(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder() .register(IT1.class, general) .register(IT1.class, specific, ""specific"", ""foo"") .register(IT1.class, general) .restrictTo(IT1.class, ""specific"") .build(); assertThat(contextRegistry.getContext(IT1.class)).isEqualTo(specific); } @Test public void testLastRegisteredHasPriorityWithIdentifier() throws Exception { AC2 context1 = new AC2(); AC2 context2 = new AC2(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder() .register(IT1.class, context1, ""foo"") .register(IT1.class, context2, ""foo"") .restrictTo(IT1.class, ""foo"") .build(); assertThat(contextRegistry.getContext(IT1.class)).isEqualTo(context2); } @Test public void testUsedNotification() throws Exception { RecordingContext context = new RecordingContext(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder() .register(RecordingContext.class, context) .register(RecordingContext.class, context) .build(); contextRegistry.notifyUsed(); assertThat(context.usedCalls).isEqualTo(1); } @Test public void testEmptyRestriction() throws Exception { AC2 general = new AC2(); AC2 specific = new AC2(); ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder() .register(IT1.class, general) .register(IT1.class, specific, ""specific"", ""foo"") .register(IT1.class, general) .restrictTo(IT1.class, ""specific"") .restrictTo(IT1.class, """") .build(); assertThat(contextRegistry.getContext(IT1.class)).isEqualTo(general); } @Test public void testNoMatch() throws Exception { ModuleActionContextRegistry contextRegistry = ModuleActionContextRegistry.builder().register(AC1.class, new AC1()).build(); assertThat(contextRegistry.getContext(IT1.class)).isNull(); } @Test public void testUnfulfilledRestriction() { AC2 context1 = new AC2(); AC2 context2 = new AC2(); ModuleActionContextRegistry.Builder builder = ModuleActionContextRegistry.builder() .register(IT1.class, context1, ""foo"") .register(IT1.class, context2, ""baz"", ""boz"") .restrictTo(IT1.class, ""bar""); AbruptExitException exception = assertThrows(AbruptExitException.class, builder::build); assertThat(exception).hasMessageThat().containsMatch(""IT1.*bar.*[foo, baz, boz]""); } private static class AC1 implements ActionContext {} private interface IT1 extends ActionContext {} private static class AC2 implements IT1 {} private static class RecordingContext implements ActionContext { private int usedCalls = 0; @Override public void usedContext(ActionContext.ActionContextRegistry [MASK] ) { usedCalls++; } } } ","actionContextRegistry " "// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.worker; import com.google.common.base.Throwables; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import java.io.IOException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.pool2.impl.EvictionPolicy; import org.apache.commons.pool2.impl.GenericKeyedObjectPool; /** Implementation of WorkerPool. */ @ThreadSafe public class WorkerPoolImpl implements WorkerPool { /** Unless otherwise specified, the max number of workers per WorkerKey. */ private static final int DEFAULT_MAX_WORKERS = 4; /** Unless otherwise specified, the max number of multiplex workers per WorkerKey. */ private static final int DEFAULT_MAX_MULTIPLEX_WORKERS = 8; private final WorkerPoolConfig workerPoolConfig; /** Map of singleplex worker pools, one per mnemonic. */ private final ImmutableMap workerPools; /** Map of multiplex worker pools, one per mnemonic. */ private final ImmutableMap multiplexPools; /** Set of worker ids which are going to be destroyed after they are returned to the pool */ private ImmutableSet doomedWorkers = ImmutableSet.of(); public WorkerPoolImpl(WorkerPoolConfig workerPoolConfig) { this.workerPoolConfig = workerPoolConfig; ImmutableMap config = createConfigFromOptions(workerPoolConfig.getWorkerMaxInstances(), DEFAULT_MAX_WORKERS); ImmutableMap multiplexConfig = createConfigFromOptions( workerPoolConfig.getWorkerMaxMultiplexInstances(), DEFAULT_MAX_MULTIPLEX_WORKERS); workerPools = createWorkerPools(workerPoolConfig.getWorkerFactory(), config); multiplexPools = createWorkerPools(workerPoolConfig.getWorkerFactory(), multiplexConfig); } public WorkerPoolConfig getWorkerPoolConfig() { return workerPoolConfig; } /** * Creates a configuration for a worker pool from the options given. If the same mnemonic occurs * more than once in the options, the last value passed wins. */ @Nonnull private static ImmutableMap createConfigFromOptions( List> options, int defaultMaxWorkers) { LinkedHashMap newConfigBuilder = new LinkedHashMap<>(); for (Map.Entry entry : options) { if (entry.getValue() != null) { newConfigBuilder.put(entry.getKey(), entry.getValue()); } else if (entry.getKey() != null) { newConfigBuilder.put(entry.getKey(), defaultMaxWorkers); } } if (!newConfigBuilder.containsKey("""")) { // Empty string gives the number of workers for any type of worker not explicitly specified. // If no value is given, use the default. newConfigBuilder.put("""", defaultMaxWorkers); } return ImmutableMap.copyOf(newConfigBuilder); } private static ImmutableMap createWorkerPools( WorkerFactory factory, Map config) { ImmutableMap.Builder [MASK] = ImmutableMap.builder(); config.forEach( (key, value) -> [MASK] .put(key, new SimpleWorkerPool(factory, value))); return [MASK] .build(); } private SimpleWorkerPool getPool(WorkerKey key) { if (key.isMultiplex()) { return multiplexPools.getOrDefault(key.getMnemonic(), multiplexPools.get("""")); } else { return workerPools.getOrDefault(key.getMnemonic(), workerPools.get("""")); } } @Override public int getMaxTotalPerKey(WorkerKey key) { return getPool(key).getMaxTotalPerKey(key); } public int getNumIdlePerKey(WorkerKey key) { return getPool(key).getNumIdle(key); } @Override public int getNumActive(WorkerKey key) { return getPool(key).getNumActive(key); } // TODO (b/242835648) filter throwed exceptions better @Override public void evictWithPolicy(EvictionPolicy evictionPolicy) throws InterruptedException { for (SimpleWorkerPool pool : workerPools.values()) { evictWithPolicy(evictionPolicy, pool); } for (SimpleWorkerPool pool : multiplexPools.values()) { evictWithPolicy(evictionPolicy, pool); } } private void evictWithPolicy(EvictionPolicy evictionPolicy, SimpleWorkerPool pool) throws InterruptedException { try { pool.setEvictionPolicy(evictionPolicy); pool.evict(); } catch (Throwable t) { Throwables.propagateIfPossible(t, InterruptedException.class); throw new VerifyException(""unexpected"", t); } } /** * Gets a worker from worker pool. Could wait if no idle workers are available. * * @param key worker key * @return a worker */ @Override public Worker borrowObject(WorkerKey key) throws IOException, InterruptedException { Worker result; try { result = getPool(key).borrowObject(key); } catch (Throwable t) { Throwables.propagateIfPossible(t, IOException.class, InterruptedException.class); throw new RuntimeException(""unexpected"", t); } return result; } @Override public void returnObject(WorkerKey key, Worker obj) { if (doomedWorkers.contains(obj.getWorkerId())) { obj.setDoomed(true); } getPool(key).returnObject(key, obj); } @Override public void invalidateObject(WorkerKey key, Worker obj) throws InterruptedException { if (doomedWorkers.contains(obj.getWorkerId())) { obj.setDoomed(true); } try { getPool(key).invalidateObject(key, obj); } catch (Throwable t) { Throwables.propagateIfPossible(t, InterruptedException.class); throw new RuntimeException(""unexpected"", t); } } @Override public synchronized void setDoomedWorkers(ImmutableSet workerIds) { this.doomedWorkers = workerIds; } /** Clear set of doomed workers. Also reset all shrunk subtrahend of all worker pools. */ @Override public synchronized void clearDoomedWorkers() { this.doomedWorkers = ImmutableSet.of(); for (SimpleWorkerPool pool : workerPools.values()) { pool.clearShrunkBy(); } for (SimpleWorkerPool pool : multiplexPools.values()) { pool.clearShrunkBy(); } } ImmutableSet getDoomedWorkers() { return doomedWorkers; } @Override public void setEventBus(EventBus eventBus) { for (SimpleWorkerPool pool : workerPools.values()) { pool.setEventBus(eventBus); } for (SimpleWorkerPool pool : multiplexPools.values()) { pool.setEventBus(eventBus); } } /** * Closes all the worker pools, destroying the workers in the process. This waits for any * currently-ongoing work to finish. */ @Override public void close() { workerPools.values().forEach(GenericKeyedObjectPool::close); multiplexPools.values().forEach(GenericKeyedObjectPool::close); } /** * Describes the configuration of worker pool, e.g. number of maximal instances and priority of * the workers. */ public static class WorkerPoolConfig { private final WorkerFactory workerFactory; private final List> workerMaxInstances; private final List> workerMaxMultiplexInstances; public WorkerPoolConfig( WorkerFactory workerFactory, List> workerMaxInstances, List> workerMaxMultiplexInstances) { this.workerFactory = workerFactory; this.workerMaxInstances = workerMaxInstances; this.workerMaxMultiplexInstances = workerMaxMultiplexInstances; } public WorkerFactory getWorkerFactory() { return workerFactory; } public List> getWorkerMaxInstances() { return workerMaxInstances; } public List> getWorkerMaxMultiplexInstances() { return workerMaxMultiplexInstances; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof WorkerPoolConfig)) { return false; } WorkerPoolConfig that = (WorkerPoolConfig) o; return workerFactory.equals(that.workerFactory) && workerMaxInstances.equals(that.workerMaxInstances) && workerMaxMultiplexInstances.equals(that.workerMaxMultiplexInstances); } @Override public int hashCode() { return Objects.hash(workerFactory, workerMaxInstances, workerMaxMultiplexInstances); } } } ","workerPoolsBuilder " "/* * Copyright 2015 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.realm; import android.util.Log; import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.jetbrains.annotations.NotNull; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.internal.util.collections.Sets; import java.io.IOException; import java.lang.reflect.Field; import java.math.BigDecimal; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.realm.entities.AllJavaTypesUnsupportedTypes; import io.realm.entities.AllTypes; import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.Cat; import io.realm.entities.CatOwner; import io.realm.entities.DictionaryAllTypes; import io.realm.entities.Dog; import io.realm.entities.IndexedFields; import io.realm.entities.KeywordFieldNames; import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.NullTypes; import io.realm.entities.Owner; import io.realm.entities.PrimaryKeyAsBoxedByte; import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.StringOnly; import io.realm.entities.embedded.EmbeddedSimpleChild; import io.realm.entities.embedded.EmbeddedSimpleParent; import io.realm.entities.realmname.ClassWithValueDefinedNames; import io.realm.exceptions.RealmException; import io.realm.log.RealmLog; import io.realm.rule.RunTestInLooperThread; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public class RealmQueryTests extends QueryTests { private void populateTestRealm(Realm testRealm, int dataSize) { testRealm.beginTransaction(); testRealm.deleteAll(); for (int i = 0; i < dataSize; ++i) { AllTypes allTypes = testRealm.createObject(AllTypes.class); allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDate(new Date(DECADE_MILLIS * (i - (dataSize / 2)))); allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.2345f + i); allTypes.setColumnString(""test data "" + i); allTypes.setColumnLong(i); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(i + "".23456789""))); allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(i))); allTypes.setColumnRealmAny(RealmAny.valueOf(i)); NonLatinFieldNames nonLatinFieldNames = testRealm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); nonLatinFieldNames.set베타(1.2345f + i); nonLatinFieldNames.setΒήτα(1.2345f + i); Dog dog = testRealm.createObject(Dog.class); dog.setAge(i); dog.setName(""test data "" + i); allTypes.setColumnRealmObject(dog); } testRealm.commitTransaction(); } private void populateTestRealm() { populateTestRealm(realm, TEST_DATA_SIZE); } private void populateNoPrimaryKeyNullTypesRows(Realm testRealm, int dataSize) { testRealm.beginTransaction(); testRealm.deleteAll(); for (int i = 0; i < dataSize; ++i) { NoPrimaryKeyNullTypes noPrimaryKeyNullTypes = testRealm.createObject(NoPrimaryKeyNullTypes.class); noPrimaryKeyNullTypes.setFieldStringNull((i % 3) == 0 ? null : ""test data "" + i); noPrimaryKeyNullTypes.setFieldStringNotNull(""test data "" + i); noPrimaryKeyNullTypes.setFieldBooleanNull((i % 3) == 0 ? null : (i % 3) == 1); noPrimaryKeyNullTypes.setFieldBooleanNotNull((i % 3) == 0); noPrimaryKeyNullTypes.setFieldByteNull((i % 3) == 0 ? null : (byte) i); noPrimaryKeyNullTypes.setFieldByteNotNull((byte) i); noPrimaryKeyNullTypes.setFieldShortNull((i % 3) == 0 ? null : (short) i); noPrimaryKeyNullTypes.setFieldShortNotNull((short) i); noPrimaryKeyNullTypes.setFieldIntegerNull((i % 3) == 0 ? null : i); noPrimaryKeyNullTypes.setFieldIntegerNotNull(i); noPrimaryKeyNullTypes.setFieldLongNull((i % 3) == 0 ? null : (long) i); noPrimaryKeyNullTypes.setFieldLongNotNull((long) i); noPrimaryKeyNullTypes.setFieldFloatNull((i % 3) == 0 ? null : 1.2345f + i); noPrimaryKeyNullTypes.setFieldFloatNotNull(1.2345f + i); noPrimaryKeyNullTypes.setFieldDoubleNull((i % 3) == 0 ? null : Math.PI + i); noPrimaryKeyNullTypes.setFieldDoubleNotNull(Math.PI + i); noPrimaryKeyNullTypes.setFieldDateNull((i % 3) == 0 ? null : new Date(DECADE_MILLIS * (i - (dataSize / 2)))); noPrimaryKeyNullTypes.setFieldDateNotNull(new Date(DECADE_MILLIS * (i - (dataSize / 2)))); } testRealm.commitTransaction(); } private void populateNoPrimaryKeyNullTypesRows() { populateNoPrimaryKeyNullTypesRows(realm, TEST_NO_PRIMARY_KEY_NULL_TYPES_SIZE); } private enum ThreadConfinedMethods { EQUAL_TO_STRING, EQUAL_TO_STRING_WITH_CASE, EQUAL_TO_BYTE, EQUAL_TO_BYTE_ARRAY, EQUAL_TO_SHORT, EQUAL_TO_INTEGER, EQUAL_TO_LONG, EQUAL_TO_DOUBLE, EQUAL_TO_FLOAT, EQUAL_TO_BOOLEAN, EQUAL_TO_DATE, IN_STRING, IN_STRING_WITH_CASE, IN_BYTE, IN_SHORT, IN_INTEGER, IN_LONG, IN_DOUBLE, IN_FLOAT, IN_BOOLEAN, IN_DATE, NOT_EQUAL_TO_STRING, NOT_EQUAL_TO_STRING_WITH_CASE, NOT_EQUAL_TO_BYTE, NOT_EQUAL_TO_BYTE_ARRAY, NOT_EQUAL_TO_SHORT, NOT_EQUAL_TO_INTEGER, NOT_EQUAL_TO_LONG, NOT_EQUAL_TO_DOUBLE, NOT_EQUAL_TO_FLOAT, NOT_EQUAL_TO_BOOLEAN, NOT_EQUAL_TO_DATE, GREATER_THAN_INTEGER, GREATER_THAN_LONG, GREATER_THAN_DOUBLE, GREATER_THAN_FLOAT, GREATER_THAN_DATE, GREATER_THAN_OR_EQUAL_TO_INTEGER, GREATER_THAN_OR_EQUAL_TO_LONG, GREATER_THAN_OR_EQUAL_TO_DOUBLE, GREATER_THAN_OR_EQUAL_TO_FLOAT, GREATER_THAN_OR_EQUAL_TO_DATE, LESS_THAN_INTEGER, LESS_THAN_LONG, LESS_THAN_DOUBLE, LESS_THAN_FLOAT, LESS_THAN_DATE, LESS_THAN_OR_EQUAL_TO_INTEGER, LESS_THAN_OR_EQUAL_TO_LONG, LESS_THAN_OR_EQUAL_TO_DOUBLE, LESS_THAN_OR_EQUAL_TO_FLOAT, LESS_THAN_OR_EQUAL_TO_DATE, BETWEEN_INTEGER, BETWEEN_LONG, BETWEEN_DOUBLE, BETWEEN_FLOAT, BETWEEN_DATE, CONTAINS_STRING, CONTAINS_STRING_WITH_CASE, BEGINS_WITH_STRING, BEGINS_WITH_STRING_WITH_CASE, ENDS_WITH_STRING, ENDS_WITH_STRING_WITH_CASE, LIKE_STRING, LIKE_STRING_WITH_CASE, BEGIN_GROUP, END_GROUP, OR, AND, NOT, IS_NULL, IS_NOT_NULL, IS_EMPTY, IS_NOT_EMPTY, IS_VALID, DISTINCT, DISTINCT_BY_MULTIPLE_FIELDS, SUM, AVERAGE, MIN, MINIMUM_DATE, MAX, MAXIMUM_DATE, COUNT, FIND_ALL, FIND_ALL_ASYNC, SORT, SORT_WITH_ORDER, SORT_WITH_MANY_ORDERS, FIND_FIRST, FIND_FIRST_ASYNC, CONTAINS_KEY, CONTAINS_VALUE, CONTAINS_ENTRY, } private static void callThreadConfinedMethod(RealmQuery query, ThreadConfinedMethods method) { switch (method) { case EQUAL_TO_STRING: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value""); break; case EQUAL_TO_STRING_WITH_CASE: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value"", Case.INSENSITIVE); break; case EQUAL_TO_BYTE: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_BYTE, (byte) 1); break; case EQUAL_TO_BYTE_ARRAY: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_BINARY, new byte[] {0, 1, 2}); break; case EQUAL_TO_SHORT: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_SHORT, (short) 1); break; case EQUAL_TO_INTEGER: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_INT, 1); break; case EQUAL_TO_LONG: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_LONG, 1L); break; case EQUAL_TO_DOUBLE: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, 1D); break; case EQUAL_TO_FLOAT: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, 1F); break; case EQUAL_TO_BOOLEAN: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN, true); break; case EQUAL_TO_DATE: query.equalTo( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date(0L)); break; case IN_STRING: query.in( AllJavaTypesUnsupportedTypes.FIELD_STRING, new String[] {""dummy value1"", ""dummy value2""}); break; case IN_STRING_WITH_CASE: query.in( AllJavaTypesUnsupportedTypes.FIELD_STRING, new String[] {""dummy value1"", ""dummy value2""}, Case.INSENSITIVE); break; case IN_BYTE: query.in( AllJavaTypesUnsupportedTypes.FIELD_BYTE, new Byte[] {1, 2, 3}); break; case IN_SHORT: query.in( AllJavaTypesUnsupportedTypes.FIELD_SHORT, new Short[] {1, 2, 3}); break; case IN_INTEGER: query.in( AllJavaTypesUnsupportedTypes.FIELD_INT, new Integer[] {1, 2, 3}); break; case IN_LONG: query.in( AllJavaTypesUnsupportedTypes.FIELD_LONG, new Long[] {1L, 2L, 3L}); break; case IN_DOUBLE: query.in( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, new Double[] {1D, 2D, 3D}); break; case IN_FLOAT: query.in( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, new Float[] {1F, 2F, 3F}); break; case IN_BOOLEAN: query.in( AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN, new Boolean[] {true, false}); break; case IN_DATE: query.in( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date[] {new Date(0L)}); break; case NOT_EQUAL_TO_STRING: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value""); break; case NOT_EQUAL_TO_STRING_WITH_CASE: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value"", Case.INSENSITIVE); break; case NOT_EQUAL_TO_BYTE: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_BYTE, (byte) 1); break; case NOT_EQUAL_TO_BYTE_ARRAY: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_BINARY, new byte[] {1,2,3}); break; case NOT_EQUAL_TO_SHORT: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_SHORT, (short) 1); break; case NOT_EQUAL_TO_INTEGER: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_INT, 1); break; case NOT_EQUAL_TO_LONG: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_LONG, 1L); break; case NOT_EQUAL_TO_DOUBLE: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, 1D); break; case NOT_EQUAL_TO_FLOAT: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, 1F); break; case NOT_EQUAL_TO_BOOLEAN: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN, true); break; case NOT_EQUAL_TO_DATE: query.notEqualTo( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date(0L)); break; case GREATER_THAN_INTEGER: query.greaterThan( AllJavaTypesUnsupportedTypes.FIELD_INT, 1); break; case GREATER_THAN_LONG: query.greaterThan( AllJavaTypesUnsupportedTypes.FIELD_LONG, 1L); break; case GREATER_THAN_DOUBLE: query.greaterThan( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, 1D); break; case GREATER_THAN_FLOAT: query.greaterThan( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, 1F); break; case GREATER_THAN_DATE: query.greaterThan( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date(0L)); break; case GREATER_THAN_OR_EQUAL_TO_INTEGER: query.greaterThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_INT, 1); break; case GREATER_THAN_OR_EQUAL_TO_LONG: query.greaterThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_LONG, 1L); break; case GREATER_THAN_OR_EQUAL_TO_DOUBLE: query.greaterThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, 1D); break; case GREATER_THAN_OR_EQUAL_TO_FLOAT: query.greaterThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, 1F); break; case GREATER_THAN_OR_EQUAL_TO_DATE: query.greaterThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date(0L)); break; case LESS_THAN_INTEGER: query.lessThan( AllJavaTypesUnsupportedTypes.FIELD_INT, 1); break; case LESS_THAN_LONG: query.lessThan( AllJavaTypesUnsupportedTypes.FIELD_LONG, 1L); break; case LESS_THAN_DOUBLE: query.lessThan( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, 1D); break; case LESS_THAN_FLOAT: query.lessThan( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, 1F); break; case LESS_THAN_DATE: query.lessThan( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date(0L)); break; case LESS_THAN_OR_EQUAL_TO_INTEGER: query.lessThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_INT, 1); break; case LESS_THAN_OR_EQUAL_TO_LONG: query.lessThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_LONG, 1L); break; case LESS_THAN_OR_EQUAL_TO_DOUBLE: query.lessThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, 1D); break; case LESS_THAN_OR_EQUAL_TO_FLOAT: query.lessThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, 1F); break; case LESS_THAN_OR_EQUAL_TO_DATE: query.lessThanOrEqualTo( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date(0L)); break; case BETWEEN_INTEGER: query.between( AllJavaTypesUnsupportedTypes.FIELD_INT, 1, 100); break; case BETWEEN_LONG: query.between( AllJavaTypesUnsupportedTypes.FIELD_LONG, 1L, 100L); break; case BETWEEN_DOUBLE: query.between( AllJavaTypesUnsupportedTypes.FIELD_DOUBLE, 1D, 100D); break; case BETWEEN_FLOAT: query.between( AllJavaTypesUnsupportedTypes.FIELD_FLOAT, 1F, 100F); break; case BETWEEN_DATE: query.between( AllJavaTypesUnsupportedTypes.FIELD_DATE, new Date(0L), new Date(10000L)); break; case CONTAINS_STRING: query.contains( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value""); break; case CONTAINS_STRING_WITH_CASE: query.contains( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value"", Case.INSENSITIVE); break; case BEGINS_WITH_STRING: query.beginsWith( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value""); break; case BEGINS_WITH_STRING_WITH_CASE: query.beginsWith( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value"", Case.INSENSITIVE); break; case ENDS_WITH_STRING: query.endsWith( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value""); break; case ENDS_WITH_STRING_WITH_CASE: query.endsWith( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value"", Case.INSENSITIVE); break; case LIKE_STRING: query.like( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value""); break; case LIKE_STRING_WITH_CASE: query.like( AllJavaTypesUnsupportedTypes.FIELD_STRING, ""dummy value"", Case.INSENSITIVE); break; case BEGIN_GROUP: query.beginGroup(); break; case END_GROUP: query.endGroup(); break; case OR: query.or(); break; case AND: query.and(); break; case NOT: query.not(); break; case IS_NULL: query.isNull( AllJavaTypesUnsupportedTypes.FIELD_DATE); break; case IS_NOT_NULL: query.isNotNull( AllJavaTypesUnsupportedTypes.FIELD_DATE); break; case IS_EMPTY: query.isEmpty( AllJavaTypesUnsupportedTypes.FIELD_STRING); break; case IS_NOT_EMPTY: query.isNotEmpty( AllJavaTypesUnsupportedTypes.FIELD_STRING); break; case IS_VALID: query.isValid(); break; case DISTINCT: query.distinct( AllJavaTypesUnsupportedTypes.FIELD_STRING); break; case DISTINCT_BY_MULTIPLE_FIELDS: query.distinct( AllJavaTypesUnsupportedTypes.FIELD_STRING, AllJavaTypesUnsupportedTypes.FIELD_ID); break; case SUM: query.sum( AllJavaTypesUnsupportedTypes.FIELD_INT); break; case AVERAGE: query.average( AllJavaTypesUnsupportedTypes.FIELD_INT); break; case MIN: query.min( AllJavaTypesUnsupportedTypes.FIELD_INT); break; case MINIMUM_DATE: query.minimumDate( AllJavaTypesUnsupportedTypes.FIELD_INT); break; case MAX: query.max( AllJavaTypesUnsupportedTypes.FIELD_INT); break; case MAXIMUM_DATE: query.maximumDate( AllJavaTypesUnsupportedTypes.FIELD_INT); break; case COUNT: query.count(); break; case FIND_ALL: query.findAll(); break; case FIND_ALL_ASYNC: query.findAllAsync(); break; case SORT: query.sort(AllJavaTypesUnsupportedTypes.FIELD_STRING); break; case SORT_WITH_ORDER: query.sort(AllJavaTypesUnsupportedTypes.FIELD_STRING, Sort.ASCENDING); break; case SORT_WITH_MANY_ORDERS: query.sort(new String[] {AllJavaTypesUnsupportedTypes.FIELD_STRING, AllJavaTypesUnsupportedTypes.FIELD_ID}, new Sort[] {Sort.DESCENDING, Sort.DESCENDING}); break; case FIND_FIRST: query.findFirst(); break; case FIND_FIRST_ASYNC: query.findFirstAsync(); break; case CONTAINS_KEY: query.containsKey(AllJavaTypesUnsupportedTypes.FIELD_STRING, null); break; case CONTAINS_VALUE: query.containsValue(AllJavaTypesUnsupportedTypes.FIELD_STRING, (String) null); break; case CONTAINS_ENTRY: query.containsEntry(AllJavaTypesUnsupportedTypes.FIELD_STRING, new AbstractMap.SimpleImmutableEntry<>(null, null)); break; default: throw new AssertionError(""missing case for "" + method); } } // The purpose of this test case is to catch when insert supports objects containing dictionaries, // to then we can use AllJavaTypes instead of AllJavaTypesUnsupportedTypes. // See: https://github.com/realm/realm-java/issues/7435 @Test(expected = IllegalStateException.class) public void catchInsertSupportsDictionaries(){ DictionaryAllTypes dictionaryAllTypes = new DictionaryAllTypes(); realm.insert(dictionaryAllTypes); } // The purpose of this test case is to catch when insert supports objects containing dictionaries, // to then we can use AllJavaTypes instead of AllJavaTypesUnsupportedTypes. // See: https://github.com/realm/realm-java/issues/7435 @Test(expected = IllegalStateException.class) public void catchInsertOrUpdateSupportsDictionaries(){ DictionaryAllTypes dictionaryAllTypes = new DictionaryAllTypes(); realm.insertOrUpdate(dictionaryAllTypes); } @Test public void callThreadConfinedMethodsFromWrongThread() throws Throwable { final RealmQuery query = realm.where(AllJavaTypesUnsupportedTypes.class); final CountDownLatch testFinished = new CountDownLatch(1); final String expectedMessage; //noinspection TryWithIdenticalCatches try { final Field expectedMessageField = BaseRealm.class.getDeclaredField(""INCORRECT_THREAD_MESSAGE""); expectedMessageField.setAccessible(true); expectedMessage = (String) expectedMessageField.get(null); } catch (NoSuchFieldException e) { throw new AssertionError(e); } catch (IllegalAccessException e) { throw new AssertionError(e); } final Thread thread = new Thread(""callThreadConfinedMethodsFromWrongThread"") { @Override public void run() { try { for (ThreadConfinedMethods method : ThreadConfinedMethods.values()) { try { callThreadConfinedMethod(query, method); fail(""IllegalStateException must be thrown.""); } catch (IllegalStateException e) { assertEquals(expectedMessage, e.getMessage()); } } } finally { testFinished.countDown(); } } }; thread.start(); TestHelper.awaitOrFail(testFinished); } @Test public void between() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) .between(AllTypes.FIELD_LONG, 0, 9).findAll(); assertEquals(10, resultList.size()); resultList = realm.where(AllTypes.class).beginsWith(AllTypes.FIELD_STRING, ""test data "").findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); resultList = realm.where(AllTypes.class).beginsWith(AllTypes.FIELD_STRING, ""test data 1"") .between(AllTypes.FIELD_LONG, 2, 20).findAll(); assertEquals(10, resultList.size()); resultList = realm.where(AllTypes.class).between(AllTypes.FIELD_LONG, 2, 20) .beginsWith(AllTypes.FIELD_STRING, ""test data 1"").findAll(); assertEquals(10, resultList.size()); assertEquals(51, realm.where(AllTypes.class).between(AllTypes.FIELD_DATE, new Date(0), new Date(DECADE_MILLIS * 50)).count()); } @Test public void greaterThan() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) .greaterThan(AllTypes.FIELD_FLOAT, 10.2345f).findAll(); assertEquals(TEST_OBJECTS_COUNT - 10, resultList.size()); resultList = realm.where(AllTypes.class).beginsWith(AllTypes.FIELD_STRING, ""test data 1"") .greaterThan(AllTypes.FIELD_FLOAT, 150.2345f).findAll(); assertEquals(TEST_OBJECTS_COUNT - 150, resultList.size()); RealmQuery query = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_FLOAT, 11.2345f); resultList = query.between(AllTypes.FIELD_LONG, 1, 20).findAll(); assertEquals(10, resultList.size()); } @Test public void greaterThan_date() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList; resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_DATE, new Date(Long.MIN_VALUE)).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * -80)).findAll(); assertEquals(179, resultList.size()); resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_DATE, new Date(0)).findAll(); assertEquals(TEST_OBJECTS_COUNT / 2 - 1, resultList.size()); resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * 80)).findAll(); assertEquals(19, resultList.size()); resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_DATE, new Date(Long.MAX_VALUE)).findAll(); assertEquals(0, resultList.size()); } @Test public void greaterThanOrEqualTo() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 10.2345f).findAll(); assertEquals(TEST_OBJECTS_COUNT - 9, resultList.size()); resultList = realm.where(AllTypes.class).beginsWith(AllTypes.FIELD_STRING, ""test data 1"") .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 50.2345f).findAll(); assertEquals(TEST_OBJECTS_COUNT - 100, resultList.size()); RealmQuery query = realm.where(AllTypes.class) .greaterThanOrEqualTo(AllTypes.FIELD_FLOAT, 11.2345f); query = query.between(AllTypes.FIELD_LONG, 1, 20); resultList = query.beginsWith(AllTypes.FIELD_STRING, ""test data 15"").findAll(); assertEquals(1, resultList.size()); } @Test public void greaterThanOrEqualTo_date() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList; resultList = realm.where(AllTypes.class).greaterThanOrEqualTo(AllTypes.FIELD_DATE, new Date(Long.MIN_VALUE)).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); resultList = realm.where(AllTypes.class).greaterThanOrEqualTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * -80)).findAll(); assertEquals(180, resultList.size()); resultList = realm.where(AllTypes.class).greaterThanOrEqualTo(AllTypes.FIELD_DATE, new Date(0)).findAll(); assertEquals(TEST_OBJECTS_COUNT / 2, resultList.size()); resultList = realm.where(AllTypes.class).greaterThanOrEqualTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * 80)).findAll(); assertEquals(20, resultList.size()); resultList = realm.where(AllTypes.class).greaterThanOrEqualTo(AllTypes.FIELD_DATE, new Date(Long.MAX_VALUE)).findAll(); assertEquals(0, resultList.size()); } @Test public void or() { populateTestRealm(realm, 200); RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.2345f); RealmResults resultList = query.or().between(AllTypes.FIELD_LONG, 1, 20).findAll(); assertEquals(21, resultList.size()); resultList = query.or().equalTo(AllTypes.FIELD_STRING, ""test data 15"").findAll(); assertEquals(21, resultList.size()); resultList = query.or().equalTo(AllTypes.FIELD_STRING, ""test data 117"").findAll(); assertEquals(22, resultList.size()); } @Test(expected = UnsupportedOperationException.class) public void or_missingFilters() { realm.where(AllTypes.class).or().findAll(); } @Test(expected = UnsupportedOperationException.class) public void or_missingFilterBefore() { realm.where(AllTypes.class).or().equalTo(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); } @Test(expected = UnsupportedOperationException.class) public void or_missingFilterAfter() { realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.2345f).or().findAll(); } @Test public void not() { populateTestRealm(); // create TEST_DATA_SIZE objects // Only one object with value 5 -> TEST_DATA_SIZE-1 object with value ""not 5"". RealmResults list1 = realm.where(AllTypes.class).not().equalTo(AllTypes.FIELD_LONG, 5).findAll(); assertEquals(TEST_DATA_SIZE - 1, list1.size()); // not().greater() and lessThenOrEqual() must be the same. RealmResults list2 = realm.where(AllTypes.class).not().greaterThan(AllTypes.FIELD_LONG, 5).findAll(); RealmResults list3 = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_LONG, 5).findAll(); assertEquals(list2.size(), list3.size()); for (int i = 0; i < list2.size(); i++) { assertEquals(list2.get(i).getColumnLong(), list3.get(i).getColumnLong()); } // excepted result: 0, 1, 2, 5 long expected[] = {0, 1, 2, 5}; RealmResults list4 = realm.where(AllTypes.class) .equalTo(AllTypes.FIELD_LONG, 5) .or() .not().beginGroup() .greaterThan(AllTypes.FIELD_LONG, 2) .endGroup() .findAll(); assertEquals(4, list4.size()); for (int i = 0; i < list4.size(); i++) { assertEquals(expected[i], list4.get(i).getColumnLong()); } } @Test(expected = UnsupportedOperationException.class) public void not_aloneThrows() { // a not() alone must fail realm.where(AllTypes.class).not().findAll(); } @Test public void and_implicit() { populateTestRealm(realm, 200); RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.2345f); RealmResults resultList = query.between(AllTypes.FIELD_LONG, 1, 10).findAll(); assertEquals(0, resultList.size()); query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 81.2345f); resultList = query.between(AllTypes.FIELD_LONG, 1, 100).findAll(); assertEquals(1, resultList.size()); } @Test public void and_explicit() { populateTestRealm(realm, 200); RealmQuery query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 31.2345f); RealmResults resultList = query.and().between(AllTypes.FIELD_LONG, 1, 10).findAll(); assertEquals(0, resultList.size()); query = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 81.2345f); resultList = query.and().between(AllTypes.FIELD_LONG, 1, 100).findAll(); assertEquals(1, resultList.size()); } @Test public void lessThan() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class). lessThan(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); assertEquals(30, resultList.size()); RealmQuery query = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_FLOAT, 31.2345f); resultList = query.between(AllTypes.FIELD_LONG, 1, 10).findAll(); assertEquals(10, resultList.size()); } @Test public void lessThan_Date() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList; resultList = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_DATE, new Date(Long.MIN_VALUE)).findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * -80)).findAll(); assertEquals(20, resultList.size()); resultList = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_DATE, new Date(0)).findAll(); assertEquals(TEST_OBJECTS_COUNT / 2, resultList.size()); resultList = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * 80)).findAll(); assertEquals(180, resultList.size()); resultList = realm.where(AllTypes.class).lessThan(AllTypes.FIELD_DATE, new Date(Long.MAX_VALUE)).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); } @Test public void lessThanOrEqualTo() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) .lessThanOrEqualTo(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); assertEquals(31, resultList.size()); resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_FLOAT, 31.2345f) .between(AllTypes.FIELD_LONG, 11, 20).findAll(); assertEquals(10, resultList.size()); } @Test public void lessThanOrEqualTo_date() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList; resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_DATE, new Date(Long.MIN_VALUE)).findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * -80)).findAll(); assertEquals(21, resultList.size()); resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_DATE, new Date(0)).findAll(); assertEquals(TEST_OBJECTS_COUNT / 2 + 1, resultList.size()); resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * 80)).findAll(); assertEquals(181, resultList.size()); resultList = realm.where(AllTypes.class).lessThanOrEqualTo(AllTypes.FIELD_DATE, new Date(Long.MAX_VALUE)).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); } @Test public void equalTo() { populateTestRealm(realm, 200); RealmResults resultList = realm.where(AllTypes.class) .equalTo(AllTypes.FIELD_FLOAT, 31.2345f).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_FLOAT, 11.0f) .equalTo(AllTypes.FIELD_LONG, 10).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_FLOAT, 11.0f) .equalTo(AllTypes.FIELD_LONG, 1).findAll(); assertEquals(0, resultList.size()); } @Test public void equalTo_decimal128() { populateTestRealm(realm, 10); for (int i = 0; i < 10; i++) { RealmResults resultList = realm.where(AllTypes.class) .equalTo(AllTypes.FIELD_DECIMAL128, new Decimal128(new BigDecimal(i + "".23456789""))) .sort(AllTypes.FIELD_DECIMAL128, Sort.ASCENDING) .findAll(); assertEquals(1, resultList.size()); assertEquals(new Decimal128(new BigDecimal(i + "".23456789"")), resultList.get(0).getColumnDecimal128()); } } @Test public void equalTo_objectId() { populateTestRealm(realm, 10); for (int i = 0; i < 10; i++) { RealmResults resultList = realm.where(AllTypes.class) .equalTo(AllTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(i))) .sort(AllTypes.FIELD_OBJECT_ID, Sort.ASCENDING) .findAll(); assertEquals(1, resultList.size()); assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(i)), resultList.get(0).getColumnObjectId()); } } @Test public void equalTo_UUID() { populateTestRealm(realm, 10); for (int i = 0; i < 10; i++) { RealmResults resultList = realm .where(AllTypes.class) .equalTo(AllTypes.FIELD_UUID, UUID.fromString(TestHelper.generateUUIDString(i))) .sort(AllTypes.FIELD_UUID, Sort.ASCENDING) .findAll(); assertEquals(1, resultList.size()); assertEquals(UUID.fromString(TestHelper.generateUUIDString(i)), resultList.get(0).getColumnUUID()); } } @Test public void notEqualTo_objectId() { populateTestRealm(realm, 10); RealmResults resultList = realm .where(AllTypes.class) .notEqualTo(AllTypes.FIELD_OBJECT_ID, new ObjectId(TestHelper.generateObjectIdHexString(0))) .sort(AllTypes.FIELD_OBJECT_ID, Sort.ASCENDING) .findAll(); assertEquals(9, resultList.size()); for (int i = 1; i < 10; i++) { assertNotEquals(new ObjectId(TestHelper.generateObjectIdHexString(0)), resultList.get(0).getColumnObjectId()); } } @Test public void notEqualTo_decimal128() { populateTestRealm(realm, 10); RealmResults resultList = realm .where(AllTypes.class) .notEqualTo(AllTypes.FIELD_DECIMAL128, new Decimal128(new BigDecimal(""0.23456789""))) .sort(AllTypes.FIELD_UUID, Sort.ASCENDING) .findAll(); assertEquals(9, resultList.size()); for (int i = 1; i < 10; i++) { assertNotEquals(new Decimal128(new BigDecimal(""0.23456789"")), resultList.get(0).getColumnDecimal128()); } } @Test public void notEqualTo_UUID() { populateTestRealm(realm, 10); RealmResults resultList = realm .where(AllTypes.class) .notEqualTo(AllTypes.FIELD_UUID, UUID.fromString(""007ba5ca-aa12-4afa-9219-e20cc3018599"")) .sort(AllTypes.FIELD_UUID, Sort.ASCENDING) .findAll(); assertEquals(10, resultList.size()); for (int i = 0; i < 10; i++) { assertNotEquals(UUID.fromString(""007ba5ca-aa12-4afa-9219-e20cc3018599""), resultList.get(0).getColumnUUID()); } } @Test public void equalTo_date() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList; resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_DATE, new Date(Long.MIN_VALUE)).findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * -80)).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_DATE, new Date(0)).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * 80)).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_DATE, new Date(Long.MAX_VALUE)).findAll(); assertEquals(0, resultList.size()); } @Test public void equalTo_nonLatinCharacters() { populateTestRealm(realm, 200); RealmResults resultList = realm.where(NonLatinFieldNames.class) .equalTo(NonLatinFieldNames.FIELD_LONG_KOREAN_CHAR, 13).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NonLatinFieldNames.class) .greaterThan(NonLatinFieldNames.FIELD_FLOAT_KOREAN_CHAR, 11.0f) .equalTo(NonLatinFieldNames.FIELD_LONG_KOREAN_CHAR, 10).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NonLatinFieldNames.class) .greaterThan(NonLatinFieldNames.FIELD_FLOAT_KOREAN_CHAR, 11.0f) .equalTo(NonLatinFieldNames.FIELD_LONG_KOREAN_CHAR, 1).findAll(); assertEquals(0, resultList.size()); resultList = realm.where(NonLatinFieldNames.class) .equalTo(NonLatinFieldNames.FIELD_LONG_GREEK_CHAR, 13).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NonLatinFieldNames.class) .greaterThan(NonLatinFieldNames.FIELD_FLOAT_GREEK_CHAR, 11.0f) .equalTo(NonLatinFieldNames.FIELD_LONG_GREEK_CHAR, 10).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NonLatinFieldNames.class) .greaterThan(NonLatinFieldNames.FIELD_FLOAT_GREEK_CHAR, 11.0f) .equalTo(NonLatinFieldNames.FIELD_LONG_GREEK_CHAR, 1).findAll(); assertEquals(0, resultList.size()); } private void doTestForInString(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[] {""test data 14""}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[] {""test data 14"", ""test data 118"", ""test data 31"", ""test data 199""}).findAll(); assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[] {""TEST data 14"", ""test data 118"", ""test data 31"", ""test DATA 199""}, Case.INSENSITIVE).findAll(); assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new String[] {""TEST data 14"", ""test data 118"", ""test data 31"", ""test DATA 199""}, Case.INSENSITIVE).findAll(); assertEquals(196, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new String[] {""TEST data 14"", ""test data 118"", ""test data 31"", ""test DATA 199""}, Case.INSENSITIVE).findAll(); assertEquals(196, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (String[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new String[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInBoolean(String targetField, int expected1, int expected2, int expected3, int expected4) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[] {false}).findAll(); assertEquals(expected1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[] {true}).findAll(); assertEquals(expected2, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[] {true, false}).findAll(); assertEquals(expected3, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Boolean[] {true, false}).findAll(); assertEquals(expected4, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Boolean[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Boolean[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInDate(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[] {new Date(DECADE_MILLIS * -80)}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[] {new Date(0)}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[] {new Date(DECADE_MILLIS * -80), new Date(0)}).findAll(); assertEquals(2, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Date[] {new Date(DECADE_MILLIS * -80), new Date(0)}).findAll(); assertEquals(198, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Date[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Date[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInDouble(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[] {Math.PI + 1}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[] {Math.PI + 2}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[] {Math.PI + 1, Math.PI + 2}).findAll(); assertEquals(2, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Double[] {Math.PI + 1, Math.PI + 2}).findAll(); assertEquals(198, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Double[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Double[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInFloat(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[] {1.2345f + 1}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[] {1.2345f + 2}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[] {1.2345f + 1, 1.2345f + 2}).findAll(); assertEquals(2, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Float[] {1.2345f + 1, 1.2345f + 2}).findAll(); assertEquals(198, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Float[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInByte(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[] {11}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[] {13}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[] {11, 13, 16, 98}).findAll(); assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Byte[] {11, 13, 16, 98}).findAll(); assertEquals(196, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Byte[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Byte[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInShort(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Short[] {11}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Short[] {4}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Short[] {2, 4, 5, 8}).findAll(); assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Short[] {2, 4, 5, 8}).findAll(); assertEquals(196, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Float[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Float[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInInteger(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[] {11}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[] {1}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[] {1, 2, 4, 5}).findAll(); assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Integer[] {1, 2, 4, 5}).findAll(); assertEquals(196, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Integer[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Integer[] {}).findAll(); assertTrue(resultList.isEmpty()); } private void doTestForInLong(String targetField) { populateNoPrimaryKeyNullTypesRows(); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[] {11l}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[] {13l}).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[] {13l, 14l, 16l, 98l}).findAll(); assertEquals(4, resultList.size()); resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(targetField, new Long[] {13l, 14l, 16l, 98l}).findAll(); assertEquals(196, resultList.size()); // Empty input always produces zero results resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, (Long[]) null).findAll(); assertTrue(resultList.isEmpty()); resultList = realm.where(NoPrimaryKeyNullTypes.class).in(targetField, new Long[] {}).findAll(); assertTrue(resultList.isEmpty()); } @Test public void in_stringNull() { doTestForInString(NoPrimaryKeyNullTypes.FIELD_STRING_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_STRING_NULL, new String[] {""TEST data 14"", ""test data 118"", null, ""test DATA 199""}, Case.INSENSITIVE).findAll(); assertEquals(130, resultList.size()); } @Test public void in_booleanNotNull() { doTestForInBoolean(NoPrimaryKeyNullTypes.FIELD_BOOLEAN_NOT_NULL, 133, 67, 200, 0); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_BOOLEAN_NOT_NULL, new Boolean[] {true, null, false}).findAll(); assertEquals(0, resultList.size()); } @Test public void in_booleanNull() { doTestForInBoolean(NoPrimaryKeyNullTypes.FIELD_BOOLEAN_NULL, 66, 67, 133, 67); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_BOOLEAN_NULL, new Boolean[] {true, null, false}).findAll(); assertEquals(0, resultList.size()); } @Test public void in_dateNotNull() { doTestForInDate(NoPrimaryKeyNullTypes.FIELD_DATE_NOT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DATE_NOT_NULL, new Date[] {new Date(DECADE_MILLIS * -80), null, new Date(0)}).findAll(); assertEquals(198, resultList.size()); } @Test public void in_dateNull() { doTestForInDate(NoPrimaryKeyNullTypes.FIELD_DATE_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DATE_NULL, new Date[] {new Date(DECADE_MILLIS * -80), null, new Date(0)}).findAll(); assertEquals(131, resultList.size()); } @Test public void in_doubleNotNull() { doTestForInDouble(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NOT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NOT_NULL, new Double[] {Math.PI + 1, null, Math.PI + 2}).findAll(); assertEquals(198, resultList.size()); } @Test public void in_doubleNull() { doTestForInDouble(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_DOUBLE_NULL, new Double[] {Math.PI + 1, null, Math.PI + 2}).findAll(); assertEquals(131, resultList.size()); } @Test public void in_floatNotNull() { doTestForInFloat(NoPrimaryKeyNullTypes.FIELD_FLOAT_NOT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_FLOAT_NOT_NULL, new Float[] {1.2345f + 1, null, 1.2345f + 2}).findAll(); assertEquals(198, resultList.size()); } @Test public void in_floatNull() { doTestForInFloat(NoPrimaryKeyNullTypes.FIELD_FLOAT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_FLOAT_NULL, new Float[] {1.2345f + 1, null, 1.2345f + 2}).findAll(); assertEquals(131, resultList.size()); } @Test public void in_byteNotNull() { doTestForInByte(NoPrimaryKeyNullTypes.FIELD_BYTE_NOT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_BYTE_NOT_NULL, new Byte[] {11, null, 13, 99}).findAll(); assertEquals(197, resultList.size()); } @Test public void in_byteNull() { doTestForInByte(NoPrimaryKeyNullTypes.FIELD_BYTE_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_BYTE_NULL, new Byte[] {11, null, 13, 99}).findAll(); assertEquals(131, resultList.size()); } @Test public void in_shortNotNull() { doTestForInShort(NoPrimaryKeyNullTypes.FIELD_SHORT_NOT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_SHORT_NOT_NULL, new Short[] {2, null, 5, 8}).findAll(); assertEquals(197, resultList.size()); } @Test public void in_shortNull() { doTestForInShort(NoPrimaryKeyNullTypes.FIELD_SHORT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_SHORT_NULL, new Short[] {2, null, 5, 8}).findAll(); assertEquals(130, resultList.size()); } @Test public void in_integerNotNull() { doTestForInInteger(NoPrimaryKeyNullTypes.FIELD_INTEGER_NOT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_INTEGER_NOT_NULL, new Integer[] {1, null, 4, 5}).findAll(); assertEquals(197, resultList.size()); } @Test public void in_integerNull() { doTestForInInteger(NoPrimaryKeyNullTypes.FIELD_INTEGER_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_INTEGER_NULL, new Integer[] {1, null, 4, 5}).findAll(); assertEquals(130, resultList.size()); } @Test public void in_longNotNull() { doTestForInLong(NoPrimaryKeyNullTypes.FIELD_LONG_NOT_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_LONG_NOT_NULL, new Long[] {13l, null, 16l, 98l}).findAll(); assertEquals(197, resultList.size()); } @Test public void in_longNull() { doTestForInLong(NoPrimaryKeyNullTypes.FIELD_LONG_NULL); RealmResults resultList = realm.where(NoPrimaryKeyNullTypes.class).not().in(NoPrimaryKeyNullTypes.FIELD_LONG_NULL, new Long[] {13l, null, 16l, 98l}).findAll(); assertEquals(130, resultList.size()); } @Test public void notEqualTo() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) .notEqualTo(AllTypes.FIELD_LONG, 31).findAll(); assertEquals(TEST_OBJECTS_COUNT - 1, resultList.size()); resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_FLOAT, 11.2345f) .equalTo(AllTypes.FIELD_LONG, 10).findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_FLOAT, 11.2345f) .equalTo(AllTypes.FIELD_LONG, 1).findAll(); assertEquals(1, resultList.size()); } @Test public void notEqualTo_date() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList; resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_DATE, new Date(Long.MIN_VALUE)).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * -80)).findAll(); assertEquals(TEST_OBJECTS_COUNT - 1, resultList.size()); resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_DATE, new Date(0)).findAll(); assertEquals(TEST_OBJECTS_COUNT - 1, resultList.size()); resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_DATE, new Date(DECADE_MILLIS * 80)).findAll(); assertEquals(TEST_OBJECTS_COUNT - 1, resultList.size()); resultList = realm.where(AllTypes.class).notEqualTo(AllTypes.FIELD_DATE, new Date(Long.MAX_VALUE)).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); } @Test public void contains_caseSensitive() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class) .contains(""columnString"", ""DaTa 0"", Case.INSENSITIVE) .or().contains(""columnString"", ""20"") .findAll(); assertEquals(3, resultList.size()); resultList = realm.where(AllTypes.class).contains(""columnString"", ""DATA"").findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class) .contains(""columnString"", ""TEST"", Case.INSENSITIVE).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); } @Test public void contains_caseSensitiveWithNonLatinCharacters() { populateTestRealm(); realm.beginTransaction(); realm.delete(AllTypes.class); AllTypes at1 = realm.createObject(AllTypes.class); at1.setColumnString(""Αλφα""); AllTypes at2 = realm.createObject(AllTypes.class); at2.setColumnString(""βήτα""); AllTypes at3 = realm.createObject(AllTypes.class); at3.setColumnString(""δέλτα""); realm.commitTransaction(); RealmResults resultList = realm.where(AllTypes.class) .contains(""columnString"", ""Α"", Case.INSENSITIVE) .or().contains(""columnString"", ""δ"") .findAll(); // Without case sensitive there is 3, Α = α // assertEquals(3,resultList.size()); assertEquals(2, resultList.size()); resultList = realm.where(AllTypes.class).contains(""columnString"", ""α"").findAll(); assertEquals(3, resultList.size()); resultList = realm.where(AllTypes.class).contains(""columnString"", ""Δ"").findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).contains(""columnString"", ""Δ"", Case.INSENSITIVE).findAll(); // Without case sensitive there is 1, Δ = δ // assertEquals(1,resultList.size()); assertEquals(0, resultList.size()); } @Test public void like_caseSensitive() { final int TEST_OBJECTS_COUNT = 200; populateTestRealm(realm, TEST_OBJECTS_COUNT); RealmResults resultList = realm.where(AllTypes.class).like(""columnString"", ""*DaTa*"").findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*DaTa*"", Case.INSENSITIVE).findAll(); assertEquals(TEST_OBJECTS_COUNT, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*DaTa 2?"").findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*DaTa 2?"", Case.INSENSITIVE).findAll(); assertEquals(10, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""TEST*0"").findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""TEST*0"", Case.INSENSITIVE).findAll(); assertEquals(20, resultList.size()); } @Test public void like_caseSensitiveWithNonLatinCharacters() { populateTestRealm(); String flagEmoji = new StringBuilder().append(Character.toChars(0x1F1E9)).toString(); String emojis = ""ABC"" + flagEmoji + ""DEF""; realm.beginTransaction(); realm.delete(AllTypes.class); AllTypes at1 = realm.createObject(AllTypes.class); at1.setColumnString(""Αλφα""); AllTypes at2 = realm.createObject(AllTypes.class); at2.setColumnString(""βήτα""); AllTypes at3 = realm.createObject(AllTypes.class); at3.setColumnString(""δέλτα""); AllTypes at4 = realm.createObject(AllTypes.class); at4.setColumnString(emojis); realm.commitTransaction(); RealmResults resultList = realm.where(AllTypes.class).like(""columnString"", ""*Α*"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*λ*"").findAll(); assertEquals(2, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*Δ*"").findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*Α*"", Case.INSENSITIVE).findAll(); // without ASCII-only limitation A matches α // assertEquals(3, resultList.size()); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*λ*"", Case.INSENSITIVE).findAll(); assertEquals(2, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*Δ*"", Case.INSENSITIVE).findAll(); // without ASCII-only limitation Δ matches δ // assertEquals(1, resultList.size()); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""?λ*"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""??λ*"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""?λ*"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""??λ*"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""ABC?DEF*"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).like(""columnString"", ""*"" + flagEmoji + ""*"").findAll(); assertEquals(1, resultList.size()); } @Test public void equalTo_withNonExistingField() { try { realm.where(AllTypes.class).equalTo(""NotAField"", 13).findAll(); fail(""Should throw exception""); } catch (IllegalArgumentException ignored) { } } @Test public void queryLink() { realm.beginTransaction(); Owner owner = realm.createObject(Owner.class); Dog dog1 = realm.createObject(Dog.class); dog1.setName(""Dog 1""); dog1.setWeight(1); Dog dog2 = realm.createObject(Dog.class); dog2.setName(""Dog 2""); dog2.setWeight(2); owner.getDogs().add(dog1); owner.getDogs().add(dog2); realm.commitTransaction(); // Dog.weight has index 4 which is more than the total number of columns in Owner // This tests exposes a subtle error where the Owner table spec is used instead of Dog table spec. RealmResults dogs = realm.where(Owner.class).findFirst().getDogs().where() .sort(""name"", Sort.ASCENDING) .findAll(); Dog dog = dogs.where().equalTo(""weight"", 1d).findFirst(); assertEquals(dog1, dog); } @Test public void sort_multiFailures() { // Zero fields specified. try { realm.where(AllTypes.class).sort(new String[] {}, new Sort[] {}).findAll(); fail(); } catch (IllegalArgumentException ignored) { } // Number of fields and sorting orders don't match. try { realm.where(AllTypes.class) .sort(new String[] {AllTypes.FIELD_STRING}, new Sort[] {Sort.ASCENDING, Sort.ASCENDING}) .findAll(); fail(); } catch (IllegalArgumentException ignored) { } // Null is not allowed. try { realm.where(AllTypes.class) .sort((String[]) null, null) .findAll(); fail(); } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class) .sort(new String[] {AllTypes.FIELD_STRING}, null) .findAll(); fail(); } catch (IllegalArgumentException ignored) { } // Non-existing field name. try { realm.where(AllTypes.class) .sort(new String[] {AllTypes.FIELD_STRING, ""do-not-exist""}, new Sort[] {Sort.ASCENDING, Sort.ASCENDING}) .findAll(); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void sort_singleField() { realm.beginTransaction(); for (int i = 0; i < TEST_DATA_SIZE; i++) { AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnLong(i); } realm.commitTransaction(); RealmResults sortedList = realm.where(AllTypes.class) .sort(new String[] {AllTypes.FIELD_LONG}, new Sort[] {Sort.DESCENDING}) .findAll(); assertEquals(TEST_DATA_SIZE, sortedList.size()); assertEquals(TEST_DATA_SIZE - 1, sortedList.first().getColumnLong()); assertEquals(0, sortedList.last().getColumnLong()); } @Test public void subQueryScope() { populateTestRealm(); RealmResults result = realm.where(AllTypes.class).lessThan(""columnLong"", 5).findAll(); RealmResults subQueryResult = result.where().greaterThan(""columnLong"", 3).findAll(); assertEquals(1, subQueryResult.size()); } @Test public void findFirst() { realm.beginTransaction(); Owner owner1 = realm.createObject(Owner.class); owner1.setName(""Owner 1""); Dog dog1 = realm.createObject(Dog.class); dog1.setName(""Dog 1""); dog1.setWeight(1); Dog dog2 = realm.createObject(Dog.class); dog2.setName(""Dog 2""); dog2.setWeight(2); owner1.getDogs().add(dog1); owner1.getDogs().add(dog2); Owner owner2 = realm.createObject(Owner.class); owner2.setName(""Owner 2""); Dog dog3 = realm.createObject(Dog.class); dog3.setName(""Dog 3""); dog3.setWeight(1); Dog dog4 = realm.createObject(Dog.class); dog4.setName(""Dog 4""); dog4.setWeight(2); owner2.getDogs().add(dog3); owner2.getDogs().add(dog4); realm.commitTransaction(); RealmList dogs = realm.where(Owner.class).equalTo(""name"", ""Owner 2"").findFirst().getDogs(); Dog dog = dogs.where().equalTo(""name"", ""Dog 4"").findFirst(); assertEquals(dog4, dog); } @Test public void findFirst_withSorting() { realm.beginTransaction(); realm.insert(new Dog(""Milo"")); realm.insert(new Dog(""Fido"")); realm.insert(new Dog(""Bella"")); realm.commitTransaction(); Dog dog = realm.where(Dog.class).sort(""name"").findFirst(); assertEquals(""Bella"", dog.getName()); } @Test public void findFirst_withSortedConstrictingView() { realm.beginTransaction(); realm.insert(new Dog(""Milo"")); realm.insert(new Dog(""Fido"")); realm.insert(new Dog(""Bella"")); realm.commitTransaction(); RealmResults dogs = realm.where(Dog.class) .in(""name"", new String[] {""Fido"", ""Bella""}) .sort(""name"", Sort.ASCENDING) .findAll(); Dog dog = dogs.where().findFirst(); assertEquals(""Bella"", dog.getName()); } @Test public void findFirst_subQuery_withSorting() { realm.beginTransaction(); realm.insert(new Dog(""Milo"")); realm.insert(new Dog(""Fido"")); realm.insert(new Dog(""Bella"")); realm.commitTransaction(); RealmResults dogs = realm.where(Dog.class).in(""name"", new String[] {""Fido"", ""Bella""}).findAll(); Dog dog = dogs.where().sort(""name"", Sort.ASCENDING).findFirst(); assertEquals(""Bella"", dog.getName()); } @Test public void georgian() { String words[] = {""მონაცემთა ბაზა"", ""მიწისქვეშა გადასასვლელი"", ""რუსთაველის გამზირი"", ""მთავარი ქუჩა"", ""სადგურის მოედანი"", ""ველოცირაპტორების ჯოგი""}; String sorted[] = {""ველოცირაპტორების ჯოგი"", ""მთავარი ქუჩა"", ""მიწისქვეშა გადასასვლელი"", ""მონაცემთა ბაზა"", ""რუსთაველის გამზირი"", ""სადგურის მოედანი""}; realm.beginTransaction(); realm.delete(StringOnly.class); for (String word : words) { StringOnly stringOnly = realm.createObject(StringOnly.class); stringOnly.setChars(word); } realm.commitTransaction(); RealmResults stringOnlies1 = realm.where(StringOnly.class).contains(""chars"", ""მთავარი"").findAll(); assertEquals(1, stringOnlies1.size()); RealmResults stringOnlies2 = realm.where(StringOnly.class).findAll(); stringOnlies2 = stringOnlies2.sort(""chars""); for (int i = 0; i < stringOnlies2.size(); i++) { assertEquals(sorted[i], stringOnlies2.get(i).getChars()); } } // Queries nullable PrimaryKey. @Test public void equalTo_nullPrimaryKeys() { final long SECONDARY_FIELD_NUMBER = 49992417L; final String SECONDARY_FIELD_STRING = ""Realm is a mobile database hundreds of millions of people rely on.""; // Fills up a Realm with one user PrimaryKey value and 9 numeric values, starting from -5. TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 10, -5); TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, SECONDARY_FIELD_STRING, 10, -5); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, SECONDARY_FIELD_STRING, 10, -5); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, SECONDARY_FIELD_STRING, 10, -5); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, SECONDARY_FIELD_STRING, 10, -5); // String assertEquals(SECONDARY_FIELD_NUMBER, realm.where(PrimaryKeyAsString.class).equalTo(PrimaryKeyAsString.FIELD_PRIMARY_KEY, (String) null).findAll().first().getId()); // Boxed Byte assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedByte.class).equalTo(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY, (Byte) null).findAll().first().getName()); // Boxed Short assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedShort.class).equalTo(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY, (Short) null).findAll().first().getName()); // Boxed Integer assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedInteger.class).equalTo(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY, (Integer) null).findAll().first().getName()); // Boxed Long assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedLong.class).equalTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, (Long) null).findAll().first().getName()); } @Test public void isNull_nullPrimaryKeys() { final long SECONDARY_FIELD_NUMBER = 49992417L; final String SECONDARY_FIELD_STRING = ""Realm is a mobile database hundreds of millions of people rely on.""; // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 10, -5); TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, SECONDARY_FIELD_STRING, 10, -5); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, SECONDARY_FIELD_STRING, 10, -5); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, SECONDARY_FIELD_STRING, 10, -5); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, SECONDARY_FIELD_STRING, 10, -5); // String assertEquals(SECONDARY_FIELD_NUMBER, realm.where(PrimaryKeyAsString.class).isNull(PrimaryKeyAsString.FIELD_PRIMARY_KEY).findAll().first().getId()); // Boxed Byte assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedByte.class).isNull(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY).findAll().first().getName()); // Boxed Short assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedShort.class).isNull(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY).findAll().first().getName()); // Boxed Integer assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedInteger.class).isNull(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY).findAll().first().getName()); // Boxed Long assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedLong.class).isNull(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY).findAll().first().getName()); } @Test public void notEqualTo_nullPrimaryKeys() { final long SECONDARY_FIELD_NUMBER = 49992417L; final String SECONDARY_FIELD_STRING = ""Realm is a mobile database hundreds of millions of people rely on.""; // Fills up a realm with one user PrimaryKey value and one numeric values, starting from -1. TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 2, -1); TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, SECONDARY_FIELD_STRING, 2, -1); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, SECONDARY_FIELD_STRING, 2, -1); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, SECONDARY_FIELD_STRING, 2, -1); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, SECONDARY_FIELD_STRING, 2, -1); // String assertEquals(SECONDARY_FIELD_NUMBER, realm.where(PrimaryKeyAsString.class).notEqualTo(PrimaryKeyAsString.FIELD_PRIMARY_KEY, ""-1"").findAll().first().getId()); // Boxed Byte assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedByte.class).notEqualTo(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY, Byte.valueOf((byte) -1)).findAll().first().getName()); // Boxed Short assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedShort.class).notEqualTo(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY, Short.valueOf((short) -1)).findAll().first().getName()); // Boxed Integer assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedInteger.class).notEqualTo(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY, Integer.valueOf(-1)).findAll().first().getName()); // Boxed Long assertEquals(SECONDARY_FIELD_STRING, realm.where(PrimaryKeyAsBoxedLong.class).notEqualTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, Long.valueOf((long) -1)).findAll().first().getName()); } @Test public void beginWith_nullStringPrimaryKey() { final long SECONDARY_FIELD_NUMBER = 49992417L; TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 10, -5); RealmQuery query = realm.where(PrimaryKeyAsString.class); try { query.beginsWith(PrimaryKeyAsString.FIELD_PRIMARY_KEY, (String) null); fail(); } catch (IllegalArgumentException ignore) { } } @Test public void contains_nullStringPrimaryKey() { final long SECONDARY_FIELD_NUMBER = 49992417L; TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 10, -5); RealmQuery query = realm.where(PrimaryKeyAsString.class); try { query.contains(PrimaryKeyAsString.FIELD_PRIMARY_KEY, (String) null); fail(); } catch (IllegalArgumentException ignore) { } } @Test public void endsWith_nullStringPrimaryKey() { final long SECONDARY_FIELD_NUMBER = 49992417L; TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 10, -5); RealmQuery query = realm.where(PrimaryKeyAsString.class); try { query.endsWith(PrimaryKeyAsString.FIELD_PRIMARY_KEY, (String) null); fail(); } catch (IllegalArgumentException ignore) { } } @Test public void like_nullStringPrimaryKey() { final long SECONDARY_FIELD_NUMBER = 49992417L; TestHelper.populateTestRealmWithStringPrimaryKey(realm, (String) null, SECONDARY_FIELD_NUMBER, 10, -5); RealmQuery query = realm.where(PrimaryKeyAsString.class); try { query.like(PrimaryKeyAsString.FIELD_PRIMARY_KEY, (String) null); fail(); } catch (IllegalArgumentException ignore) { } } @Test public void between_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, (String) null, 10, -5); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, (String) null, 10, -5); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, (String) null, 10, -5); // Boxed Byte assertEquals(3, realm.where(PrimaryKeyAsBoxedByte.class).between(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY, -1, 1).count()); // Boxed Short assertEquals(3, realm.where(PrimaryKeyAsBoxedShort.class).between(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY, -1, 1).count()); // Boxed Integer assertEquals(3, realm.where(PrimaryKeyAsBoxedInteger.class).between(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY, -1, 1).count()); // Boxed Long assertEquals(3, realm.where(PrimaryKeyAsBoxedLong.class).between(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1, 1).count()); } @Test public void greaterThan_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, (String) null, 10, -5); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, (String) null, 10, -5); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, (String) null, 10, -5); // Boxed Byte assertEquals(4, realm.where(PrimaryKeyAsBoxedByte.class).greaterThan(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY, -1).count()); // Boxed Short assertEquals(4, realm.where(PrimaryKeyAsBoxedShort.class).greaterThan(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY, -1).count()); // Boxed Integer assertEquals(4, realm.where(PrimaryKeyAsBoxedInteger.class).greaterThan(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY, -1).count()); // Boxed Long assertEquals(4, realm.where(PrimaryKeyAsBoxedLong.class).greaterThan(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1).count()); } @Test public void greaterThanOrEqualTo_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, (String) null, 10, -5); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, (String) null, 10, -5); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, (String) null, 10, -5); // Boxed Byte assertEquals(5, realm.where(PrimaryKeyAsBoxedByte.class).greaterThanOrEqualTo(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY, -1).count()); // Boxed Short assertEquals(5, realm.where(PrimaryKeyAsBoxedShort.class).greaterThanOrEqualTo(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY, -1).count()); // Boxed Integer assertEquals(5, realm.where(PrimaryKeyAsBoxedInteger.class).greaterThanOrEqualTo(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY, -1).count()); // Boxed Long assertEquals(5, realm.where(PrimaryKeyAsBoxedLong.class).greaterThanOrEqualTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, -1).count()); } @Test public void lessThan_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, (String) null, 10, -5); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, (String) null, 10, -5); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, (String) null, 10, -5); // Boxed Byte assertEquals(6, realm.where(PrimaryKeyAsBoxedByte.class).lessThan(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY, 1).count()); // Boxed Short assertEquals(6, realm.where(PrimaryKeyAsBoxedShort.class).lessThan(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY, 1).count()); // Boxed Integer assertEquals(6, realm.where(PrimaryKeyAsBoxedInteger.class).lessThan(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY, 1).count()); // Boxed Long assertEquals(6, realm.where(PrimaryKeyAsBoxedLong.class).lessThan(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, 1).count()); } @Test public void lessThanOrEqualTo_nullPrimaryKeysIsNotZero() { // Fills up a realm with one user PrimaryKey value and 9 numeric values, starting from -5. TestHelper.populateTestRealmWithBytePrimaryKey(realm, (Byte) null, (String) null, 10, -5); TestHelper.populateTestRealmWithShortPrimaryKey(realm, (Short) null, (String) null, 10, -5); TestHelper.populateTestRealmWithIntegerPrimaryKey(realm, (Integer) null, (String) null, 10, -5); TestHelper.populateTestRealmWithLongPrimaryKey(realm, (Long) null, (String) null, 10, -5); // Boxed Byte assertEquals(7, realm.where(PrimaryKeyAsBoxedByte.class).lessThanOrEqualTo(PrimaryKeyAsBoxedByte.FIELD_PRIMARY_KEY, 1).count()); // Boxed Short assertEquals(7, realm.where(PrimaryKeyAsBoxedShort.class).lessThanOrEqualTo(PrimaryKeyAsBoxedShort.FIELD_PRIMARY_KEY, 1).count()); // Boxed Integer assertEquals(7, realm.where(PrimaryKeyAsBoxedInteger.class).lessThanOrEqualTo(PrimaryKeyAsBoxedInteger.FIELD_PRIMARY_KEY, 1).count()); // Boxed Long assertEquals(7, realm.where(PrimaryKeyAsBoxedLong.class).lessThanOrEqualTo(PrimaryKeyAsBoxedLong.FIELD_PRIMARY_KEY, 1).count()); } // Queries nullable fields with equalTo null. @Test public void equalTo_nullableFields() { TestHelper.populateTestRealmForNullTests(realm); // 1 String assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_STRING_NULL, ""Horse"").count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_STRING_NULL, (String) null).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_STRING_NULL, ""Fish"").count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_STRING_NULL, ""Goat"").count()); // 2 Bytes assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTES_NULL, new byte[] {0}).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTES_NULL, (byte[]) null).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTES_NULL, new byte[] {1, 2}).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTES_NULL, new byte[] {1, 2, 3}).count()); // 3 Boolean assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BOOLEAN_NULL, true).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BOOLEAN_NULL, (Boolean) null).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BOOLEAN_NULL, false).count()); // 4 Byte assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTE_NULL, 1).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTE_NULL, (byte) 1).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTE_NULL, (Byte) null).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTE_NULL, (byte) 42).count()); // 5 Short for other long based columns, only test null assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_SHORT_NULL, 1).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_SHORT_NULL, (short) 1).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_SHORT_NULL, (Short) null).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_SHORT_NULL, (short) 42).count()); // 6 Integer assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_INTEGER_NULL, 1).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_INTEGER_NULL, (Integer) null).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_INTEGER_NULL, 42).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_LONG_NULL, 1).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_LONG_NULL, (long) 1).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_LONG_NULL, (Long) null).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_LONG_NULL, (long) 42).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_FLOAT_NULL, 1F).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_FLOAT_NULL, (Float) null).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_FLOAT_NULL, 42F).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_DOUBLE_NULL, 1D).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_DOUBLE_NULL, (Double) null).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_DOUBLE_NULL, 42D).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_DATE_NULL, new Date(0)).count()); assertEquals(1, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_DATE_NULL, (Date) null).count()); assertEquals(0, realm.where(NullTypes.class).equalTo(NullTypes.FIELD_DATE_NULL, new Date(424242)).count()); // 11 Object skipped, doesn't support equalTo query } // Queries nullable field for null. @Test public void isNull_nullableFields() { TestHelper.populateTestRealmForNullTests(realm); // 1 String assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_STRING_NULL).count()); // 2 Bytes assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_BYTES_NULL).count()); // 3 Boolean assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_BOOLEAN_NULL).count()); // 4 Byte assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_BYTE_NULL).count()); // 5 Short assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_SHORT_NULL).count()); // 6 Integer assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_INTEGER_NULL).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_LONG_NULL).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_FLOAT_NULL).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_DOUBLE_NULL).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_DATE_NULL).count()); // 11 Object assertEquals(1, realm.where(NullTypes.class).isNull(NullTypes.FIELD_OBJECT_NULL).count()); } // Queries nullable field for not null. @Test public void notEqualTo_nullableFields() { TestHelper.populateTestRealmForNullTests(realm); // 1 String assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_STRING_NULL, ""Horse"").count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_STRING_NULL, (String) null).count()); // 2 Bytes assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_BYTES_NULL, new byte[] {1, 2}).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_BYTES_NULL, (byte[]) null).count()); // 3 Boolean assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_BOOLEAN_NULL, false).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_BOOLEAN_NULL, (Boolean) null).count()); // 4 Byte assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_BYTE_NULL, (byte) 1).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_BYTE_NULL, (Byte) null).count()); // 5 Short assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_SHORT_NULL, (short) 1).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_SHORT_NULL, (Byte) null).count()); // 6 Integer assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_INTEGER_NULL, 1).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_INTEGER_NULL, (Integer) null).count()); // 7 Long assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_LONG_NULL, 1).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_LONG_NULL, (Integer) null).count()); // 8 Float assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_FLOAT_NULL, 1F).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_FLOAT_NULL, (Float) null).count()); // 9 Double assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_DOUBLE_NULL, 1D).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_DOUBLE_NULL, (Double) null).count()); // 10 Date assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_DATE_NULL, new Date(0)).count()); assertEquals(2, realm.where(NullTypes.class).notEqualTo(NullTypes.FIELD_DATE_NULL, (Date) null).count()); // 11 Object skipped, doesn't support notEqualTo query } // Queries nullable field for not null. @Test public void isNotNull_nullableFields() { TestHelper.populateTestRealmForNullTests(realm); // 1 String assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_STRING_NULL).count()); // 2 Bytes assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_BYTES_NULL).count()); // 3 Boolean assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_BOOLEAN_NULL).count()); // 4 Byte assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_BYTE_NULL).count()); // 5 Short assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_SHORT_NULL).count()); // 6 Integer assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_INTEGER_NULL).count()); // 7 Long assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_LONG_NULL).count()); // 8 Float assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_FLOAT_NULL).count()); // 9 Double assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_DOUBLE_NULL).count()); // 10 Date assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_DATE_NULL).count()); // 11 Object assertEquals(2, realm.where(NullTypes.class).isNotNull(NullTypes.FIELD_OBJECT_NULL).count()); } @Test public void isNull_differentThanEmpty() { // Make sure that isNull doesn't match empty string """" realm.executeTransaction(r -> { r.delete(NullTypes.class); NullTypes obj = new NullTypes(); obj.setId(1); obj.setFieldStringNull(null); r.insert(obj); obj = new NullTypes(); obj.setId(2); obj.setFieldStringNull(""""); r.insert(obj); obj = new NullTypes(); obj.setId(3); obj.setFieldStringNull(""foo""); r.insert(obj); }); assertEquals(3, realm.where(NullTypes.class).findAll().size()); RealmResults results = realm.where(NullTypes.class).isNull(NullTypes.FIELD_STRING_NULL).findAll(); assertEquals(1, results.size()); assertNull(results.first().getFieldStringNull()); results = realm.where(NullTypes.class).isEmpty(NullTypes.FIELD_STRING_NULL).findAll(); assertEquals(1, results.size()); assertEquals("""", results.first().getFieldStringNull()); } // Queries nullable field with beginsWith - all strings begin with null. @Test(expected = IllegalArgumentException.class) public void beginWith_nullForNullableStrings() { TestHelper.populateTestRealmForNullTests(realm); assertEquals(""Fish"", realm.where(NullTypes.class).beginsWith(NullTypes.FIELD_STRING_NULL, (String) null).findFirst().getFieldStringNotNull()); } // Queries nullable field with contains - all strings contain null. @Test(expected = IllegalArgumentException.class) public void contains_nullForNullableStrings() { TestHelper.populateTestRealmForNullTests(realm); assertEquals(""Fish"", realm.where(NullTypes.class).contains(NullTypes.FIELD_STRING_NULL, (String) null).findFirst().getFieldStringNotNull()); } // Queries nullable field with endsWith - all strings end with null. @Test(expected = IllegalArgumentException.class) public void endsWith_nullForNullableStrings() { TestHelper.populateTestRealmForNullTests(realm); assertEquals(""Fish"", realm.where(NullTypes.class).endsWith(NullTypes.FIELD_STRING_NULL, (String) null).findFirst().getFieldStringNotNull()); } // Queries nullable field with like - nulls do not match either '?' or '*'. @Test public void like_nullForNullableStrings() { TestHelper.populateTestRealmForNullTests(realm); RealmResults resultList = realm.where(NullTypes.class).like(NullTypes.FIELD_STRING_NULL, ""*"") .findAll(); assertEquals(2, resultList.size()); resultList = realm.where(NullTypes.class).like(NullTypes.FIELD_STRING_NULL, ""?"").findAll(); assertEquals(0, resultList.size()); } // Queries with between and table has null values in row. @Test public void between_nullValuesInRow() { TestHelper.populateTestRealmForNullTests(realm); // 6 Integer assertEquals(1, realm.where(NullTypes.class).between(NullTypes.FIELD_INTEGER_NULL, 2, 4).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).between(NullTypes.FIELD_LONG_NULL, 2L, 4L).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).between(NullTypes.FIELD_FLOAT_NULL, 2F, 4F).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).between(NullTypes.FIELD_DOUBLE_NULL, 2D, 4D).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).between(NullTypes.FIELD_DATE_NULL, new Date(10000), new Date(20000)).count()); } // Queries with greaterThan and table has null values in row. @Test public void greaterThan_nullValuesInRow() { TestHelper.populateTestRealmForNullTests(realm); // 6 Integer assertEquals(1, realm.where(NullTypes.class).greaterThan(NullTypes.FIELD_INTEGER_NULL, 2).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).greaterThan(NullTypes.FIELD_LONG_NULL, 2L).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).greaterThan(NullTypes.FIELD_FLOAT_NULL, 2F).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).greaterThan(NullTypes.FIELD_DOUBLE_NULL, 2D).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).greaterThan(NullTypes.FIELD_DATE_NULL, new Date(5000)).count()); } // Queries with greaterThanOrEqualTo and table has null values in row. @Test public void greaterThanOrEqualTo_nullValuesInRow() { TestHelper.populateTestRealmForNullTests(realm); // 6 Integer assertEquals(1, realm.where(NullTypes.class).greaterThanOrEqualTo(NullTypes.FIELD_INTEGER_NULL, 3).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).greaterThanOrEqualTo(NullTypes.FIELD_LONG_NULL, 3L).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).greaterThanOrEqualTo(NullTypes.FIELD_FLOAT_NULL, 3F).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).greaterThanOrEqualTo(NullTypes.FIELD_DOUBLE_NULL, 3D).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).greaterThanOrEqualTo(NullTypes.FIELD_DATE_NULL, new Date(10000)).count()); } // Queries with lessThan and table has null values in row. @Test public void lessThan_nullValuesInRow() { TestHelper.populateTestRealmForNullTests(realm); // 6 Integer assertEquals(1, realm.where(NullTypes.class).lessThan(NullTypes.FIELD_INTEGER_NULL, 2).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).lessThan(NullTypes.FIELD_LONG_NULL, 2L).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).lessThan(NullTypes.FIELD_FLOAT_NULL, 2F).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).lessThan(NullTypes.FIELD_DOUBLE_NULL, 2D).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).lessThan(NullTypes.FIELD_DATE_NULL, new Date(5000)).count()); } // Queries with lessThanOrEqualTo and table has null values in row. @Test public void lessThanOrEqual_nullValuesInRow() { TestHelper.populateTestRealmForNullTests(realm); // 6 Integer assertEquals(1, realm.where(NullTypes.class).lessThanOrEqualTo(NullTypes.FIELD_INTEGER_NULL, 1).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).lessThanOrEqualTo(NullTypes.FIELD_LONG_NULL, 1L).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).lessThanOrEqualTo(NullTypes.FIELD_FLOAT_NULL, 1F).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).lessThanOrEqualTo(NullTypes.FIELD_DOUBLE_NULL, 1D).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).lessThanOrEqualTo(NullTypes.FIELD_DATE_NULL, new Date(9999)).count()); } // If the RealmQuery is built on a TableView, it should not crash when used after GC. // See issue #1161 for more details. @Test public void buildQueryFromResultsGC() { // According to the testing, setting this to 10 can almost certainly trigger the GC. // Uses 30 here can ensure GC happen. (Tested with 4.3 1G Ram and 5.0 3G Ram) final int count = 30; RealmResults results = realm.where(CatOwner.class).findAll(); for (int i = 1; i <= count; i++) { @SuppressWarnings({""unused""}) byte garbage[] = TestHelper.allocGarbage(0); results = results.where().findAll(); System.gc(); // If a native resource has a reference count = 0, doing GC here might lead to a crash. } } private static byte[][] binaries = {{1, 2, 3}, {1, 2}, {1, 2, 3}, {2, 3}, {2}, {4, 5, 6}}; private void createBinaryOnlyDataSet() { realm.beginTransaction(); for (int i = 0; i < binaries.length; i++) { AllJavaTypesUnsupportedTypes binaryOnly = new AllJavaTypesUnsupportedTypes((long) i); binaryOnly.setFieldBinary(binaries[i]); realm.copyToRealm(binaryOnly); } realm.commitTransaction(); } @Test public void equalTo_binary() { createBinaryOnlyDataSet(); RealmResults resultList; resultList = realm.where(AllJavaTypesUnsupportedTypes.class).equalTo(AllJavaTypesUnsupportedTypes.FIELD_BINARY, binaries[0]).findAll(); assertEquals(2, resultList.size()); resultList = realm.where(AllJavaTypesUnsupportedTypes.class).equalTo(AllJavaTypesUnsupportedTypes.FIELD_BINARY, binaries[1]).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllJavaTypesUnsupportedTypes.class).equalTo(AllJavaTypesUnsupportedTypes.FIELD_BINARY, new byte[] {1}).findAll(); assertEquals(0, resultList.size()); } @Test public void equalTo_binary_multiFailures() { createBinaryOnlyDataSet(); // Non-binary field. try { RealmResults resultList = realm.where(AllJavaTypesUnsupportedTypes.class) .equalTo(AllJavaTypesUnsupportedTypes.FIELD_INT, binaries[0]).findAll(); fail(""Should throw exception.""); } catch (IllegalArgumentException ignored) { } // Non-existent field. try { RealmResults resultList = realm.where(AllJavaTypesUnsupportedTypes.class) .equalTo(""NotAField"", binaries[0]).findAll(); fail(""Should throw exception.""); } catch (IllegalArgumentException ignored) { } } @Test public void notEqualTo_binary() { createBinaryOnlyDataSet(); RealmResults resultList; resultList = realm.where(AllJavaTypesUnsupportedTypes.class).notEqualTo(AllJavaTypesUnsupportedTypes.FIELD_BINARY, binaries[0]).findAll(); assertEquals(4, resultList.size()); resultList = realm.where(AllJavaTypesUnsupportedTypes.class).notEqualTo(AllJavaTypesUnsupportedTypes.FIELD_BINARY, binaries[1]).findAll(); assertEquals(5, resultList.size()); resultList = realm.where(AllJavaTypesUnsupportedTypes.class).notEqualTo(AllJavaTypesUnsupportedTypes.FIELD_BINARY, new byte[] {1}).findAll(); assertEquals(6, resultList.size()); } @Test public void notEqualTo_binary_multiFailures() { createBinaryOnlyDataSet(); // Non-binary field. try { RealmResults resultList = realm.where(AllJavaTypesUnsupportedTypes.class) .notEqualTo(AllJavaTypesUnsupportedTypes.FIELD_INT, binaries[0]).findAll(); fail(""Should throw exception.""); } catch (IllegalArgumentException ignored) { } // Non-existent field. try { RealmResults resultList = realm.where(AllJavaTypesUnsupportedTypes.class) .notEqualTo(""NotAField"", binaries[0]).findAll(); fail(""Should throw exception.""); } catch (IllegalArgumentException ignored) { } } // Tests min on empty columns. @Test public void min_emptyColumns() { RealmQuery query = realm.where(NullTypes.class); assertNull(query.min(NullTypes.FIELD_INTEGER_NOT_NULL)); assertNull(query.min(NullTypes.FIELD_FLOAT_NOT_NULL)); assertNull(query.min(NullTypes.FIELD_DOUBLE_NOT_NULL)); assertNull(query.minimumDate(NullTypes.FIELD_DATE_NOT_NULL)); } // Tests min on columns with all null rows. @Test public void min_allNullColumns() { TestHelper.populateAllNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertNull(query.min(NullTypes.FIELD_INTEGER_NULL)); assertNull(query.min(NullTypes.FIELD_FLOAT_NULL)); assertNull(query.min(NullTypes.FIELD_DOUBLE_NULL)); assertNull(query.minimumDate(NullTypes.FIELD_DATE_NULL)); } // Tests min on columns with all non-null rows. @Test public void min_allNonNullRows() { TestHelper.populateAllNonNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(-1, query.min(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(-2f, query.min(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(-3d, query.min(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); assertEquals(-2000, query.minimumDate(NullTypes.FIELD_DATE_NULL).getTime()); } // Tests min on columns with partial null rows. @Test public void min_partialNullRows() { TestHelper.populatePartialNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(3, query.min(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(4f, query.min(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(5d, query.min(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); } // Test max on empty columns @Test public void max_emptyColumns() { RealmQuery query = realm.where(NullTypes.class); assertNull(query.max(NullTypes.FIELD_INTEGER_NOT_NULL)); assertNull(query.max(NullTypes.FIELD_FLOAT_NOT_NULL)); assertNull(query.max(NullTypes.FIELD_DOUBLE_NOT_NULL)); assertNull(query.maximumDate(NullTypes.FIELD_DATE_NOT_NULL)); } // Tests max on columns with all null rows. @Test public void max_allNullColumns() { TestHelper.populateAllNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertNull(query.max(NullTypes.FIELD_INTEGER_NULL)); assertNull(query.max(NullTypes.FIELD_FLOAT_NULL)); assertNull(query.max(NullTypes.FIELD_DOUBLE_NULL)); assertNull(query.maximumDate(NullTypes.FIELD_DATE_NULL)); } // Tests max on columns with all non-null rows. @Test public void max_allNonNullRows() { TestHelper.populateAllNonNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(4, query.max(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(5f, query.max(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(6d, query.max(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); assertEquals(12345, query.maximumDate(NullTypes.FIELD_DATE_NULL).getTime()); } // Tests max on columns with partial null rows. @Test public void max_partialNullRows() { TestHelper.populatePartialNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(4, query.max(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(5f, query.max(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(6d, query.max(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); assertEquals(12345, query.maximumDate(NullTypes.FIELD_DATE_NULL).getTime()); } // Tests average on empty columns. @Test public void average_emptyColumns() { RealmQuery query = realm.where(NullTypes.class); assertEquals(0d, query.average(NullTypes.FIELD_INTEGER_NULL), 0d); assertEquals(0d, query.average(NullTypes.FIELD_FLOAT_NULL), 0d); assertEquals(0d, query.average(NullTypes.FIELD_DOUBLE_NULL), 0d); } // Tests average on columns with all null rows. @Test public void average_allNullColumns() { TestHelper.populateAllNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(0d, query.average(NullTypes.FIELD_INTEGER_NULL), 0d); assertEquals(0d, query.average(NullTypes.FIELD_FLOAT_NULL), 0d); assertEquals(0d, query.average(NullTypes.FIELD_DOUBLE_NULL), 0d); } // Tests average on columns with all non-null rows. @Test public void average_allNonNullRows() { TestHelper.populateAllNonNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(2.0, query.average(NullTypes.FIELD_INTEGER_NULL), 0d); assertEquals(7.0 / 3, query.average(NullTypes.FIELD_FLOAT_NULL), 0.001d); assertEquals(8.0 / 3, query.average(NullTypes.FIELD_DOUBLE_NULL), 0.001d); } // Tests average on columns with partial null rows. @Test public void average_partialNullRows() { TestHelper.populatePartialNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(3.5, query.average(NullTypes.FIELD_INTEGER_NULL), 0d); assertEquals(4.5, query.average(NullTypes.FIELD_FLOAT_NULL), 0d); assertEquals(5.5, query.average(NullTypes.FIELD_DOUBLE_NULL), 0d); } // Tests sum on empty columns. @Test public void sum_emptyColumns() { RealmQuery query = realm.where(NullTypes.class); assertEquals(0, query.sum(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(0f, query.sum(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(0d, query.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); } // Tests sum on columns with all null rows. @Test public void sum_allNullColumns() { TestHelper.populateAllNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(0, query.sum(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(0f, query.sum(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(0d, query.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); } // Tests sum on columns with all non-null rows. @Test public void sum_allNonNullRows() { TestHelper.populateAllNonNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(6, query.sum(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(7f, query.sum(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(8d, query.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); } // Tests sum on columns with partial null rows. @Test public void sum_partialNullRows() { TestHelper.populatePartialNullRowsForNumericTesting(realm); RealmQuery query = realm.where(NullTypes.class); assertEquals(7, query.sum(NullTypes.FIELD_INTEGER_NULL).intValue()); assertEquals(9f, query.sum(NullTypes.FIELD_FLOAT_NULL).floatValue(), 0f); assertEquals(11d, query.sum(NullTypes.FIELD_DOUBLE_NULL).doubleValue(), 0d); } @Test public void count() { populateTestRealm(realm, TEST_DATA_SIZE); assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).count()); } // Verify that count correctly when using distinct. // See https://github.com/realm/realm-java/issues/5958 @Test public void distinctCount() { realm.executeTransaction(r -> { for (int i = 0; i < 5; i++) { AllTypes obj = new AllTypes(); obj.setColumnString(""Foo""); realm.copyToRealm(obj); } }); assertEquals(1, realm.where(AllTypes.class).distinct(AllTypes.FIELD_STRING).count()); } // Tests isNull on link's nullable field. @Test public void isNull_linkField() { TestHelper.populateTestRealmForNullTests(realm); // For the link with null value, query isNull on its fields should return true. // 1 String assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_STRING_NULL).count()); // 2 Bytes assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_BYTES_NULL).count()); // 3 Boolean assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_BOOLEAN_NULL).count()); // 4 Byte assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_BYTE_NULL).count()); // 5 Short assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_SHORT_NULL).count()); // 6 Integer assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_INTEGER_NULL).count()); // 7 Long assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_LONG_NULL).count()); // 8 Float assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_FLOAT_NULL).count()); // 9 Double assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_DOUBLE_NULL).count()); // 10 Date assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_DATE_NULL).count()); // 11 Object assertEquals(2, realm.where(NullTypes.class).isNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_OBJECT_NULL).count()); } // Tests isNotNull on link's nullable field. @Test public void isNotNull_linkField() { TestHelper.populateTestRealmForNullTests(realm); // 1 String assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_STRING_NULL).count()); // 2 Bytes assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_BYTES_NULL).count()); // 3 Boolean assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_BOOLEAN_NULL).count()); // 4 Byte assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_BYTE_NULL).count()); // 5 Short assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_SHORT_NULL).count()); // 6 Integer assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_INTEGER_NULL).count()); // 7 Long assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_LONG_NULL).count()); // 8 Float assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_FLOAT_NULL).count()); // 9 Double assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_DOUBLE_NULL).count()); // 10 Date assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_DATE_NULL).count()); // 11 Object assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_OBJECT_NULL).count()); assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_DECIMAL128_NULL).count()); assertEquals(1, realm.where(NullTypes.class).isNotNull( NullTypes.FIELD_OBJECT_NULL + ""."" + NullTypes.FIELD_OBJECT_ID_NULL).count()); } // Calling isNull on fields with the RealmList type will trigger an exception. @Test public void isNull_listFieldThrows() { try { realm.where(Owner.class).isNull(""dogs""); fail(); } catch (IllegalArgumentException expected) { assertTrue(expected.getMessage().contains(""Illegal Argument: Cannot compare linklist ('dogs') with NULL"")); } try { realm.where(Cat.class).isNull(""owner.dogs""); fail(); } catch (IllegalArgumentException expected) { assertTrue(expected.getMessage().contains(""Illegal Argument: Cannot compare linklist ('owner.dogs') with NULL"")); } } // Calling isNotNull on fields with the RealmList type will trigger an exception. @Test public void isNotNull_listFieldThrows() { try { realm.where(Owner.class).isNotNull(""dogs""); fail(); } catch (IllegalArgumentException expected) { assertTrue(expected.getMessage().contains(""Illegal Argument: Cannot compare linklist ('dogs') with NULL"")); } try { realm.where(Cat.class).isNotNull(""owner.dogs""); fail(); } catch (IllegalArgumentException expected) { assertTrue(expected.getMessage().contains(""Illegal Argument: Cannot compare linklist ('owner.dogs') with NULL"")); } } @Test public void isValid_tableQuery() { final RealmQuery query = realm.where(AllTypes.class); assertTrue(query.isValid()); populateTestRealm(realm, 1); // Still valid if result changed. assertTrue(query.isValid()); realm.close(); assertFalse(query.isValid()); } @Test public void isValid_tableViewQuery() { populateTestRealm(); final RealmQuery query = realm.where(AllTypes.class).greaterThan(AllTypes.FIELD_FLOAT, 5f) .findAll().where(); assertTrue(query.isValid()); populateTestRealm(realm, 1); // Still valid if table view changed. assertTrue(query.isValid()); realm.close(); assertFalse(query.isValid()); } // Test for https://github.com/realm/realm-java/issues/1905 @Test public void resultOfTableViewQuery() { populateTestRealm(); final RealmResults results = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 3L).findAll(); assertEquals(1, results.size()); assertEquals(""test data 3"", results.first().getColumnString()); final RealmQuery tableViewQuery = results.where(); assertEquals(""test data 3"", tableViewQuery.findAll().first().getColumnString()); assertEquals(""test data 3"", tableViewQuery.findFirst().getColumnString()); } @Test public void isValid_linkViewQuery() { populateTestRealm(realm, 1); final RealmList list = realm.where(AllTypes.class).findFirst().getColumnRealmList(); final RealmQuery query = list.where(); final long listLength = query.count(); assertTrue(query.isValid()); realm.beginTransaction(); final Dog dog = realm.createObject(Dog.class); dog.setName(""Dog""); list.add(dog); realm.commitTransaction(); // Still valid if base view changed. assertEquals(listLength + 1, query.count()); assertTrue(query.isValid()); realm.close(); assertFalse(query.isValid()); } @Test public void isValid_removedParent() { populateTestRealm(realm, 1); final AllTypes obj = realm.where(AllTypes.class).findFirst(); final RealmQuery query = obj.getColumnRealmList().where(); assertTrue(query.isValid()); realm.beginTransaction(); obj.deleteFromRealm(); realm.commitTransaction(); // Invalid if parent has been removed. assertFalse(query.isValid()); } @Test public void isEmpty() throws IOException { createIsEmptyDataSet(realm); for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case STRING: assertEquals(2, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING).count()); break; case BINARY: assertEquals(2, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY).count()); break; case LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_LIST).count()); break; case OBJECT: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT).count()); break; case INTEGER_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_LIST).count()); break; case BOOLEAN_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_LIST).count()); break; case STRING_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_LIST).count()); break; case BINARY_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_LIST).count()); break; case DATE_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_LIST).count()); break; case FLOAT_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_LIST).count()); break; case DOUBLE_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_LIST).count()); break; case DECIMAL128_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_LIST).count()); break; case OBJECT_ID_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_LIST).count()); break; case UUID_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_LIST).count()); break; case MIXED_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_LIST).count()); break; case LINKING_OBJECTS: // Row 2 does not have a backlink assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_LO_OBJECT).count()); assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_LO_LIST).count()); break; case STRING_TO_MIXED_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_DICTIONARY).count()); break; case STRING_TO_BOOLEAN_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_DICTIONARY).count()); break; case STRING_TO_STRING_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_DICTIONARY).count()); break; case STRING_TO_INTEGER_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_DICTIONARY).count()); break; case STRING_TO_FLOAT_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_DICTIONARY).count()); break; case STRING_TO_DOUBLE_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_DICTIONARY).count()); break; case STRING_TO_BINARY_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_DICTIONARY).count()); break; case STRING_TO_DATE_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_DICTIONARY).count()); break; case STRING_TO_OBJECT_ID_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_DICTIONARY).count()); break; case STRING_TO_UUID_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_DICTIONARY).count()); break; case STRING_TO_DECIMAL128_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_DICTIONARY).count()); break; case STRING_TO_LINK_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_LINK_DICTIONARY).count()); break; case MIXED_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_SET).count()); break; case BOOLEAN_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_SET).count()); break; case STRING_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_SET).count()); break; case INTEGER_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_SET).count()); break; case FLOAT_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_SET).count()); break; case DOUBLE_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_SET).count()); break; case BINARY_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_SET).count()); break; case DATE_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_SET).count()); break; case OBJECT_ID_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_SET).count()); break; case UUID_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_SET).count()); break; case DECIMAL128_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_SET).count()); break; case LINK_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_LINK_SET).count()); break; default: fail(""Unknown type: "" + type); } } } @Test public void isEmpty_acrossLink() { createIsEmptyDataSet(realm); for (RealmFieldType type : SUPPORTED_IS_EMPTY_TYPES) { switch (type) { case STRING: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_STRING).count()); break; case BINARY: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BINARY).count()); break; case LIST: // Row 0: Backlink list to row 1, list to row 0; included // Row 1: Backlink list to row 2, list to row 1; included // Row 2: No backlink list; not included assertEquals(2, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LIST).count()); break; case LINKING_OBJECTS: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LO_LIST).count()); // Row 0: Link to row 0, backlink to row 0; not included // Row 1: Link to row 1m backlink to row 1; not included // Row 2: Empty link; included assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LO_OBJECT).count()); break; case OBJECT: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_OBJECT).count()); break; case INTEGER_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_INTEGER_LIST).count()); break; case BOOLEAN_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_LIST).count()); break; case STRING_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_STRING_LIST).count()); break; case BINARY_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BINARY_LIST).count()); break; case DATE_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DATE_LIST).count()); break; case FLOAT_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_FLOAT_LIST).count()); break; case DOUBLE_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_LIST).count()); break; case DECIMAL128_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_LIST).count()); break; case OBJECT_ID_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_LIST).count()); break; case UUID_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_UUID_LIST).count()); break; case MIXED_LIST: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_LIST).count()); break; case STRING_TO_MIXED_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_DICTIONARY).count()); break; case STRING_TO_BOOLEAN_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_DICTIONARY).count()); break; case STRING_TO_STRING_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_STRING_DICTIONARY).count()); break; case STRING_TO_INTEGER_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_INTEGER_DICTIONARY).count()); break; case STRING_TO_FLOAT_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_FLOAT_DICTIONARY).count()); break; case STRING_TO_DOUBLE_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_DICTIONARY).count()); break; case STRING_TO_BINARY_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BINARY_DICTIONARY).count()); break; case STRING_TO_DATE_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DATE_DICTIONARY).count()); break; case STRING_TO_OBJECT_ID_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_DICTIONARY).count()); break; case STRING_TO_UUID_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_UUID_DICTIONARY).count()); break; case STRING_TO_DECIMAL128_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_DICTIONARY).count()); break; case STRING_TO_LINK_MAP: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LINK_DICTIONARY).count()); break; case MIXED_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_SET).count()); break; case BOOLEAN_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_SET).count()); break; case STRING_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_STRING_SET).count()); break; case INTEGER_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_INTEGER_SET).count()); break; case FLOAT_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_FLOAT_SET).count()); break; case DOUBLE_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_SET).count()); break; case BINARY_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BINARY_SET).count()); break; case DATE_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DATE_SET).count()); break; case OBJECT_ID_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_SET).count()); break; case UUID_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_UUID_SET).count()); break; case DECIMAL128_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_SET).count()); break; case LINK_SET: assertEquals(3, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LINK_SET).count()); break; default: fail(""Unknown type: "" + type); } } } @Test public void isEmpty_illegalFieldTypeThrows() { for (RealmFieldType type : NOT_SUPPORTED_IS_EMPTY_TYPES) { try { switch (type) { case INTEGER: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_LONG).findAll(); break; case FLOAT: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT).findAll(); break; case DOUBLE: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE).findAll(); break; case BOOLEAN: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN).findAll(); break; case DATE: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE).findAll(); break; case DECIMAL128: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128).findAll(); break; case OBJECT_ID: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID).findAll(); break; case UUID: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID).findAll(); break; case MIXED: realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY).findAll(); break; default: fail(""Unknown type: "" + type); } fail(type + "" should throw an exception""); } catch (IllegalArgumentException ignored) { } } } @Test public void isEmpty_invalidFieldNameThrows() { String[] fieldNames = new String[] {null, """", ""foo"", AllJavaTypesUnsupportedTypes.FIELD_OBJECT + "".foo""}; for (String fieldName : fieldNames) { try { realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(fieldName); fail(); } catch (IllegalArgumentException ignored) { } } } @Test public void isEmpty_acrossLink_wrongTypeThrows() { for (RealmFieldType type : RealmFieldType.values()) { if (SUPPORTED_IS_EMPTY_TYPES.contains(type)) { continue; } RealmQuery query = realm.where(Owner.class); try { query.isEmpty(Owner.FIELD_CAT + ""."" + Cat.FIELD_AGE); fail(); } catch (IllegalArgumentException expected) { assertTrue(expected.getMessage().contains(""Illegal Argument: Operation '@count' is not supported on property of type"")); } } } @Test public void isNotEmpty() { createIsNotEmptyDataSet(realm); for (RealmFieldType type : SUPPORTED_IS_NOT_EMPTY_TYPES) { switch (type) { case STRING: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING).count()); break; case BINARY: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY).count()); break; case LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LIST).count()); break; case LINKING_OBJECTS: assertEquals(2, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LO_OBJECT).count()); assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LO_LIST).count()); break; case OBJECT: assertEquals(2, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT).count()); break; case INTEGER_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_LIST).count()); break; case BOOLEAN_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_LIST).count()); break; case STRING_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_LIST).count()); break; case BINARY_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_LIST).count()); break; case DATE_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_LIST).count()); break; case FLOAT_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_LIST).count()); break; case DOUBLE_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_LIST).count()); break; case DECIMAL128_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_LIST).count()); break; case OBJECT_ID_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_LIST).count()); break; case UUID_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_LIST).count()); break; case MIXED_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_LIST).count()); break; case STRING_TO_MIXED_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_DICTIONARY).count()); break; case STRING_TO_BOOLEAN_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_DICTIONARY).count()); break; case STRING_TO_STRING_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_DICTIONARY).count()); break; case STRING_TO_INTEGER_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_DICTIONARY).count()); break; case STRING_TO_FLOAT_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_DICTIONARY).count()); break; case STRING_TO_DOUBLE_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_DICTIONARY).count()); break; case STRING_TO_BINARY_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_DICTIONARY).count()); break; case STRING_TO_DATE_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_DICTIONARY).count()); break; case STRING_TO_OBJECT_ID_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_DICTIONARY).count()); break; case STRING_TO_UUID_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_DICTIONARY).count()); break; case STRING_TO_DECIMAL128_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_DICTIONARY).count()); break; case STRING_TO_LINK_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LINK_DICTIONARY).count()); break; case MIXED_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_SET).count()); break; case BOOLEAN_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_SET).count()); break; case STRING_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_SET).count()); break; case INTEGER_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_SET).count()); break; case FLOAT_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_SET).count()); break; case DOUBLE_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_SET).count()); break; case BINARY_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_SET).count()); break; case DATE_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_SET).count()); break; case OBJECT_ID_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_SET).count()); break; case UUID_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_SET).count()); break; case DECIMAL128_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_SET).count()); break; case LINK_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LINK_SET).count()); break; default: fail(""Unknown type: "" + type); } } } @Test public void isNotEmpty_acrossLink() { createIsNotEmptyDataSet(realm); for (RealmFieldType type : SUPPORTED_IS_NOT_EMPTY_TYPES) { switch (type) { case STRING: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_STRING).count()); break; case BINARY: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BINARY).count()); break; case LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LIST).count()); break; case LINKING_OBJECTS: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LO_LIST).count()); assertEquals(2, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_LO_OBJECT).count()); break; case OBJECT: assertEquals(0, realm.where(AllJavaTypesUnsupportedTypes.class).isEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT).count()); break; case INTEGER_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_LIST).count()); break; case BOOLEAN_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_LIST).count()); break; case STRING_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_LIST).count()); break; case BINARY_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_LIST).count()); break; case DATE_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_LIST).count()); break; case FLOAT_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_LIST).count()); break; case DOUBLE_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_LIST).count()); break; case DECIMAL128_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_LIST).count()); break; case OBJECT_ID_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_LIST).count()); break; case UUID_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_LIST).count()); break; case MIXED_LIST: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_LIST).count()); break; case STRING_TO_MIXED_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_DICTIONARY).count()); break; case STRING_TO_BOOLEAN_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_DICTIONARY).count()); break; case STRING_TO_STRING_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_DICTIONARY).count()); break; case STRING_TO_INTEGER_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_DICTIONARY).count()); break; case STRING_TO_FLOAT_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_DICTIONARY).count()); break; case STRING_TO_DOUBLE_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_DICTIONARY).count()); break; case STRING_TO_BINARY_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_DICTIONARY).count()); break; case STRING_TO_DATE_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_DICTIONARY).count()); break; case STRING_TO_OBJECT_ID_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_DICTIONARY).count()); break; case STRING_TO_UUID_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_DICTIONARY).count()); break; case STRING_TO_DECIMAL128_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_DICTIONARY).count()); break; case STRING_TO_LINK_MAP: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LINK_DICTIONARY).count()); break; case MIXED_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY_SET).count()); break; case BOOLEAN_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN_SET).count()); break; case STRING_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_STRING_SET).count()); break; case INTEGER_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_INTEGER_SET).count()); break; case FLOAT_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT_SET).count()); break; case DOUBLE_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE_SET).count()); break; case BINARY_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BINARY_SET).count()); break; case DATE_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE_SET).count()); break; case OBJECT_ID_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID_SET).count()); break; case UUID_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID_SET).count()); break; case DECIMAL128_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128_SET).count()); break; case LINK_SET: assertEquals(1, realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LINK_SET).count()); break; default: fail(""Unknown type: "" + type); } } } @Test public void isNotEmpty_illegalFieldTypeThrows() { for (RealmFieldType type : NOT_SUPPORTED_IS_NOT_EMPTY_TYPES) { try { switch (type) { case INTEGER: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_LONG).findAll(); break; case FLOAT: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_FLOAT).findAll(); break; case DOUBLE: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DOUBLE).findAll(); break; case BOOLEAN: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN).findAll(); break; case DATE: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DATE).findAll(); break; case DECIMAL128: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_DECIMAL128).findAll(); break; case OBJECT_ID: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_OBJECT_ID).findAll(); break; case UUID: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_UUID).findAll(); break; case MIXED: realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(AllJavaTypesUnsupportedTypes.FIELD_REALM_ANY).findAll(); break; default: fail(""Unknown type: "" + type); } fail(type + "" should throw an exception""); } catch (IllegalArgumentException ignored) { } } } @Test public void isNotEmpty_invalidFieldNameThrows() { String[] fieldNames = new String[] {null, """", ""foo"", AllJavaTypesUnsupportedTypes.FIELD_OBJECT + "".foo""}; for (String fieldName : fieldNames) { try { realm.where(AllJavaTypesUnsupportedTypes.class).isNotEmpty(fieldName).findAll(); fail(); } catch (IllegalArgumentException ignored) { } } } // Tests that deep queries work on a lot of data. @Test public void deepLinkListQuery() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Crashes with i == 1000, 500, 100, 89, 85, 84. // Doesn't crash for i == 10, 50, 75, 82, 83. for (int i = 0; i < 84; i++) { AllJavaTypesUnsupportedTypes obj = realm.createObject(AllJavaTypesUnsupportedTypes.class, i + 1); obj.setFieldBoolean(i % 2 == 0); obj.setFieldObject(obj); RealmResults items = realm.where(AllJavaTypesUnsupportedTypes.class).findAll(); RealmList fieldList = obj.getFieldList(); for (int j = 0; j < items.size(); j++) { fieldList.add(items.get(j)); } } } }); for (int i = 0; i < 4; i++) { realm.where(AllJavaTypesUnsupportedTypes.class).equalTo( AllJavaTypesUnsupportedTypes.FIELD_LIST + ""."" + AllJavaTypesUnsupportedTypes.FIELD_OBJECT + ""."" + AllJavaTypesUnsupportedTypes.FIELD_BOOLEAN, true) .findAll(); } } @Test public void sort_onSubObjectField() { populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) .sort(AllTypes.FIELD_REALMOBJECT + ""."" + Dog.FIELD_AGE) .findAll(); assertEquals(0, results.get(0).getColumnRealmObject().getAge()); assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); } @Test @RunTestInLooperThread public void sort_async_onSubObjectField() { Realm realm = looperThread.getRealm(); populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) .sort(AllTypes.FIELD_REALMOBJECT + ""."" + Dog.FIELD_AGE) .findAllAsync(); looperThread.keepStrongReference(results); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { assertEquals(0, results.get(0).getColumnRealmObject().getAge()); assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); looperThread.testComplete(); } }); } @Test public void findAll_indexedCaseInsensitiveFields() { // Catches https://github.com/realm/realm-java/issues/4788 realm.beginTransaction(); realm.createObject(IndexedFields.class, new ObjectId()).indexedString = ""ROVER""; realm.createObject(IndexedFields.class, new ObjectId()).indexedString = ""Rover""; realm.commitTransaction(); RealmResults results = realm.where(IndexedFields.class) .equalTo(IndexedFields.FIELD_INDEXED_STRING, ""rover"", Case.INSENSITIVE) .findAll(); assertEquals(2, results.size()); } @Test public void sort_listOnSubObjectField() { String[] fieldNames = new String[2]; fieldNames[0] = AllTypes.FIELD_REALMOBJECT + ""."" + Dog.FIELD_AGE; fieldNames[1] = AllTypes.FIELD_REALMOBJECT + ""."" + Dog.FIELD_AGE; Sort[] sorts = new Sort[2]; sorts[0] = Sort.ASCENDING; sorts[1] = Sort.ASCENDING; populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class) .sort(fieldNames, sorts) .findAll(); assertEquals(0, results.get(0).getColumnRealmObject().getAge()); assertEquals(TEST_DATA_SIZE - 1, results.get(TEST_DATA_SIZE - 1).getColumnRealmObject().getAge()); } private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOfObjects, boolean withNull) { realm.beginTransaction(); for (int i = 0; i < numberOfObjects * numberOfBlocks; i++) { for (int j = 0; j < numberOfBlocks; j++) { AnnotationIndexTypes obj = realm.createObject(AnnotationIndexTypes.class); obj.setIndexBoolean(j % 2 == 0); obj.setIndexLong(j); obj.setIndexDate(withNull ? null : new Date(1000L * j)); obj.setIndexString(withNull ? null : ""Test "" + j); obj.setNotIndexBoolean(j % 2 == 0); obj.setNotIndexLong(j); obj.setNotIndexDate(withNull ? null : new Date(1000L * j)); obj.setNotIndexString(withNull ? null : ""Test "" + j); obj.setFieldObject(obj); } } realm.commitTransaction(); } private void populateForDistinctAllTypes(Realm realm, long numberOfBlocks, long numberOfObjects) { realm.beginTransaction(); for (int i = 0; i < numberOfBlocks; i++) { Dog dog = realm.createObject(Dog.class); for (int j = 0; j < numberOfObjects; j++) { AllTypes obj = realm.createObject(AllTypes.class); obj.setColumnBinary(new byte[j]); obj.setColumnString(""Test "" + j); obj.setColumnLong(j); obj.setColumnFloat(j / 1000f); obj.setColumnDouble(j / 1000d); obj.setColumnBoolean(j % 2 == 0); obj.setColumnDate(new Date(1000L * j)); obj.setColumnDecimal128(new Decimal128(j)); obj.setColumnObjectId(new ObjectId(j, j)); obj.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(j))); obj.setColumnMutableRealmInteger(j); obj.setColumnRealmLink(obj); obj.setColumnRealmObject(dog); obj.setColumnRealmAny(RealmAny.valueOf(i)); } } realm.commitTransaction(); } private void populateForDistinctInvalidTypesLinked(Realm realm) { realm.beginTransaction(); AllJavaTypesUnsupportedTypes notEmpty = new AllJavaTypesUnsupportedTypes(); notEmpty.setFieldBinary(new byte[] {1, 2, 3}); notEmpty.setFieldObject(notEmpty); notEmpty.setFieldList(new RealmList(notEmpty)); realm.copyToRealm(notEmpty); realm.commitTransaction(); } @Test public void distinct() { final long numberOfBlocks = 3; final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAll(); assertEquals(2, distinctBool.size()); for (String field : new String[] {AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field).findAll(); assertEquals(field, numberOfBlocks, distinct.size()); } } @Test public void distinct_withNullValues() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); for (String field : new String[] {AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING}) { RealmResults distinct = realm.where(AnnotationIndexTypes.class).distinct(field).findAll(); assertEquals(field, 1, distinct.size()); } } // Helper method to verify distinct behavior an all fields of AllTypes, potentially following // possible multiple indirection links as given by 'prefix' private void distinctAllFields(Realm realm, String prefix) { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinctAllTypes(realm, numberOfBlocks, numberOfObjects); // Dynamic realm for verifying distinct query result against naive manual implementation of // distinct DynamicRealm dynamicRealm = DynamicRealm.createInstance(realm.sharedRealm); RealmResults all = dynamicRealm.where(AllTypes.CLASS_NAME) .findAll(); // Bookkeeping to ensure that we are actually testing all types HashSet types = new HashSet(Arrays.asList(RealmFieldType.values())); types.remove(RealmFieldType.TYPED_LINK); types.remove(RealmFieldType.MIXED_LIST); types.remove(RealmFieldType.STRING_TO_MIXED_MAP); types.remove(RealmFieldType.STRING_TO_BOOLEAN_MAP); types.remove(RealmFieldType.STRING_TO_STRING_MAP); types.remove(RealmFieldType.STRING_TO_INTEGER_MAP); types.remove(RealmFieldType.STRING_TO_FLOAT_MAP); types.remove(RealmFieldType.STRING_TO_DOUBLE_MAP); types.remove(RealmFieldType.STRING_TO_BINARY_MAP); types.remove(RealmFieldType.STRING_TO_DATE_MAP); types.remove(RealmFieldType.STRING_TO_OBJECT_ID_MAP); types.remove(RealmFieldType.STRING_TO_UUID_MAP); types.remove(RealmFieldType.STRING_TO_DECIMAL128_MAP); types.remove(RealmFieldType.STRING_TO_LINK_MAP); types.remove(RealmFieldType.BOOLEAN_SET); types.remove(RealmFieldType.STRING_SET); types.remove(RealmFieldType.INTEGER_SET); types.remove(RealmFieldType.FLOAT_SET); types.remove(RealmFieldType.DOUBLE_SET); types.remove(RealmFieldType.BINARY_SET); types.remove(RealmFieldType.DATE_SET); types.remove(RealmFieldType.DECIMAL128_SET); types.remove(RealmFieldType.OBJECT_ID_SET); types.remove(RealmFieldType.UUID_SET); types.remove(RealmFieldType.LINK_SET); types.remove(RealmFieldType.MIXED_SET); // Iterate all fields of AllTypes table and verify that distinct either: // - Returns correct number of entries, or // - Raises an error that distinct cannot be performed on the specific field types (lists) RealmObjectSchema schema = realm.getSchema().getSchemaForClass(AllTypes.CLASS_NAME); Set fieldNames = schema.getFieldNames(); for (String fieldName : fieldNames) { String field = prefix + fieldName; RealmFieldType type = schema.getFieldType(fieldName); if (supportDistinct(type)) { // Actual query RealmResults distinct = realm.where(AllTypes.class) .distinct(field) .findAll(); // Assert query result // Test against manual distinct implementation Set> values = distinct(all, field); assertEquals(field, values.size(), distinct.size()); // Test against expected numbers from setup switch (type) { case BOOLEAN: assertEquals(field, 2, distinct.size()); break; case OBJECT: if (fieldName.equals(""columnRealmObject"")) { assertEquals(field, numberOfBlocks, distinct.size()); } else if (fieldName.equals(""columnRealmLink"")) { assertEquals(field, numberOfBlocks * numberOfObjects, distinct.size()); } else { fail(""Unknown object "" + fieldName); } break; default: assertEquals(field, numberOfObjects, distinct.size()); break; } } else { // Test that unsupported types throw exception as expected try { realm.where(AllTypes.class) .distinct(field) .findAll(); } catch (IllegalStateException ignore) { // Not distinct not supported on lists } } types.remove(type); } // Validate that backlinks are not supported by sort/distinct assertEquals(types.toString(), Sets.newSet(RealmFieldType.LINKING_OBJECTS), types); RealmQuery query = realm.where(AllTypes.class); try{ query.distinct(prefix + AllTypes.FIELD_REALMBACKLINK); fail(); } catch (IllegalArgumentException ignore){ } } @Test public void distinct_allFields() { distinctAllFields(realm, """"); } @Test public void distinct_linkedAllFields() { distinctAllFields(realm, AllTypes.FIELD_REALMLINK + "".""); } @Test public void distinct_nestedLinkedAllFields() { distinctAllFields(realm, AllTypes.FIELD_REALMLINK + ""."" + AllTypes.FIELD_REALMLINK + "".""); } @Test public void distinct_doesNotExist() { final long numberOfBlocks = 3; final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { realm.where(AnnotationIndexTypes.class).distinct(""doesNotExist"").findAll(); fail(); } catch (IllegalArgumentException ignored) { } } // Smoke test of async distinct. Underlying mechanism is the same as for sync test // (distinct_allFields), so just verifying async mechanism. @Test @RunTestInLooperThread public void distinct_async() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(4); final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 3; final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); final RealmResults distinctBool = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL).findAllAsync(); final RealmResults distinctLong = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_LONG).findAllAsync(); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_DATE).findAllAsync(); final RealmResults distinctString = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_STRING).findAllAsync(); assertFalse(distinctBool.isLoaded()); assertTrue(distinctBool.isValid()); assertTrue(distinctBool.isEmpty()); assertFalse(distinctLong.isLoaded()); assertTrue(distinctLong.isValid()); assertTrue(distinctLong.isEmpty()); assertFalse(distinctDate.isLoaded()); assertTrue(distinctDate.isValid()); assertTrue(distinctDate.isEmpty()); assertFalse(distinctString.isLoaded()); assertTrue(distinctString.isValid()); assertTrue(distinctString.isEmpty()); final Runnable endTest = new Runnable() { @Override public void run() { if (changeListenerCalled.decrementAndGet() == 0) { looperThread.testComplete(); } } }; looperThread.keepStrongReference(distinctBool); looperThread.keepStrongReference(distinctLong); looperThread.keepStrongReference(distinctDate); looperThread.keepStrongReference(distinctString); distinctBool.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertEquals(2, distinctBool.size()); endTest.run(); } }); distinctLong.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertEquals(numberOfBlocks, distinctLong.size()); endTest.run(); } }); distinctDate.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertEquals(numberOfBlocks, distinctDate.size()); endTest.run(); } }); distinctString.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertEquals(numberOfBlocks, distinctString.size()); endTest.run(); } }); } @Test @RunTestInLooperThread public void distinct_async_withNullValues() throws Throwable { final AtomicInteger changeListenerCalled = new AtomicInteger(2); final Realm realm = looperThread.getRealm(); final long numberOfBlocks = 3; final long numberOfObjects = 3; // must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); final RealmResults distinctDate = realm.where(AnnotationIndexTypes.class) .distinct(AnnotationIndexTypes.FIELD_INDEX_DATE) .findAllAsync(); final RealmResults distinctString = realm.where(AnnotationIndexTypes.class) .distinct(AnnotationIndexTypes.FIELD_INDEX_STRING) .findAllAsync(); final Runnable endTest = new Runnable() { @Override public void run() { if (changeListenerCalled.decrementAndGet() == 0) { looperThread.testComplete(); } } }; looperThread.keepStrongReference(distinctDate); looperThread.keepStrongReference(distinctString); distinctDate.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertEquals(1, distinctDate.size()); endTest.run(); } }); distinctString.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults object) { assertEquals(1, distinctString.size()); endTest.run(); } }); } @Test @RunTestInLooperThread public void distinct_async_doesNotExist() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); try { realm.where(AnnotationIndexTypes.class).distinct(""doesNotExist"").findAllAsync(); } catch (IllegalArgumentException ignored) { } looperThread.testComplete(); } // Smoke test of async distinct invalid types. Underlying mechanism is the same as for sync test // (distinct_allFields), so just verifying async mechanism. @Test @RunTestInLooperThread public void distinct_async_invalidTypes() { populateTestRealm(realm, TEST_DATA_SIZE); RealmObjectSchema schema = realm.getSchema().getSchemaForClass(AllTypes.CLASS_NAME); Set fieldNames = schema.getFieldNames(); for (String fieldName : fieldNames) { String field = fieldName; RealmFieldType type = schema.getFieldType(fieldName); if (!supportDistinct(type)) { try { realm.where(AllTypes.class).distinct(field).findAllAsync(); } catch (IllegalArgumentException ignored) { } } } looperThread.testComplete(); } @Test public void distinctMultiArgs() { final long numberOfBlocks = 3; final long numberOfObjects = 3; // Must be greater than 1 populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); RealmResults distinctMulti = query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.INDEX_FIELDS).findAll(); assertEquals(numberOfBlocks, distinctMulti.size()); } @Test public void distinctMultiArgs_switchedFieldsOrder() { final long numberOfBlocks = 3; TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks); // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples. RealmResults distinctStringLong = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG).findAll(); RealmResults distinctLongString = realm.where(AnnotationIndexTypes.class).distinct(AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); assertEquals(4, distinctStringLong.size()); assertEquals(4, distinctLongString.size()); assertEquals(distinctStringLong.size(), distinctLongString.size()); } @Test public void distinctMultiArgs_emptyField() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, false); RealmQuery query = realm.where(AnnotationIndexTypes.class); // An empty string field in the middle. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, """", AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); fail(); } catch (IllegalArgumentException ignored) { } // A null string field in the middle. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null, AnnotationIndexTypes.FIELD_INDEX_INT).findAll(); fail(); } catch (IllegalArgumentException ignored) { } // (String) Null makes varargs a null array. try { query.distinct(AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null).findAll(); fail(); } catch (IllegalArgumentException ignored) { } // Two (String) null for first and varargs fields. try { query.distinct((String) null, (String) null).findAll(); fail(); } catch (IllegalArgumentException ignored) { } // """" & (String) null combination. try { query.distinct("""", (String) null).findAll(); fail(); } catch (IllegalArgumentException ignored) { } // """" & (String) null combination. try { query.distinct((String) null, """").findAll(); fail(); } catch (IllegalArgumentException ignored) { } // Two empty fields tests. try { query.distinct("""", """").findAll(); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void distinctMultiArgs_withNullValues() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); RealmQuery query = realm.where(AnnotationIndexTypes.class); RealmResults distinctMulti = query.distinct(AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING).findAll(); assertEquals(1, distinctMulti.size()); } @Test public void distinctMultiArgs_LinkedFields() { final long numberOfBlocks = 3; final long numberOfObjects = 3; populateForDistinct(realm, numberOfBlocks, numberOfObjects, true); DynamicRealm dynamicRealm = DynamicRealm.createInstance(realm.sharedRealm); RealmResults all = dynamicRealm.where(AnnotationIndexTypes.CLASS_NAME) .findAll(); RealmQuery query = realm.where(AnnotationIndexTypes.class); RealmResults distinct = query.distinct(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING, AnnotationIndexTypes.INDEX_LINKED_FIELDS).findAll(); List fields = new ArrayList(); fields.add(AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING); fields.addAll(Arrays.asList(AnnotationIndexTypes.INDEX_LINKED_FIELDS)); Set> values = distinct(all, fields.toArray()); assertEquals(values.size(), distinct.size()); } @Test(expected = UnsupportedOperationException.class) public void beginGroup_missingEndGroup() { realm.where(AllTypes.class).beginGroup().findAll(); } @Test(expected = UnsupportedOperationException.class) public void multipleBeginGroup_missingEndGroup() { realm.where(AllTypes.class).beginGroup().beginGroup().endGroup().findAll(); } @Test(expected = UnsupportedOperationException.class) public void endGroup_missingBeginGroup() { realm.where(AllTypes.class).endGroup().findAll(); } @Test(expected = UnsupportedOperationException.class) public void multipleEndGroup_missingBeginGroup() { realm.where(AllTypes.class).beginGroup().endGroup().endGroup().findAll(); } @Test public void alwaysTrue() { populateTestRealm(); assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).alwaysTrue().findAll().size()); } @Test public void alwaysTrue_inverted() { populateTestRealm(); assertEquals(0, realm.where(AllTypes.class).not().alwaysTrue().findAll().size()); } @Test public void alwaysFalse() { populateTestRealm(); assertEquals(0, realm.where(AllTypes.class).alwaysFalse().findAll().size()); } @Test public void alwaysFalse_inverted() { populateTestRealm(); assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).not().alwaysFalse().findAll().size()); } @Test public void getRealm() { assertTrue(realm == realm.where(AllTypes.class).getRealm()); } @Test public void getRealm_throwsIfDynamicRealm() { DynamicRealm dRealm = DynamicRealm.getInstance(realm.getConfiguration()); try { dRealm.where(AllTypes.CLASS_NAME).getRealm(); fail(); } catch (IllegalStateException ignore) { } finally { dRealm.close(); } } @Test public void getRealm_throwsIfRealmClosed() { RealmQuery query = realm.where(AllTypes.class); realm.close(); try { query.getRealm(); fail(); } catch (IllegalStateException ignore) { } } @Test public void rawPredicate() { populateTestRealm(); RealmResults result = realm.where(AllTypes.class).rawPredicate(""columnString = 'test data 0'"").findAll(); assertEquals(1, result.size()); } @Test public void rawPredicate_invalidFieldNameThrows() { try { realm.where(AllTypes.class).rawPredicate(""foo = 'test data 0'""); fail(); } catch (IllegalArgumentException e) { assertTrue(""Real message: "" + e.getMessage(), e.getMessage().contains(""'AllTypes' has no property 'foo'"")); } } @Test public void rawPredicate_invalidLinkedFieldNameThrows() { try { realm.where(AllTypes.class).rawPredicate(""columnRealmObject.foo = 'test data 0'""); fail(); } catch (IllegalArgumentException e) { assertTrue(""Real message: "" + e.getMessage(), e.getMessage().contains(""'Dog' has no property 'foo'"")); } try { realm.where(AllTypes.class).rawPredicate(""unknownField.foo = 'test data 0'""); fail(); } catch (IllegalArgumentException e) { assertTrue(""Real message: "" + e.getMessage(), e.getMessage().contains(""'AllTypes' has no property 'unknownField'"")); } } @Test public void rawPredicate_illegalSyntaxThrows() { try { realm.where(AllTypes.class).rawPredicate(""lol""); fail(); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains(""Invalid predicate: 'lol'"")); } } @Test public void rawPredicate_invalidTypeThrows() { try { realm.where(AllTypes.class).rawPredicate(""columnString = 42.0""); fail(); } catch (IllegalArgumentException ex) { assertTrue(""Error message was: "" + ex.getMessage(), ex.getMessage().contains(""Unsupported comparison between type 'string' and type 'double'"")); } } @Test public void rawPredicate_realmAnyWithTypedPredicates() { populateTestRealm(); RealmResults result = realm.where(AllTypes.class) .equalTo(""columnString"", ""test data 0"") .or() .rawPredicate(""columnString = 'test data 1'"") .findAll(); assertEquals(2, result.size()); } @Test public void rawPredicate_rawDescriptors() { realm.beginTransaction(); realm.insert(new Dog(""Milo"")); realm.insert(new Dog(""Fido"")); realm.insert(new Dog(""Bella"")); realm.insert(new Dog(""Bella"")); realm.commitTransaction(); RealmQuery query = realm.where(Dog.class) .rawPredicate(""TRUEPREDICATE SORT(name ASC) DISTINCT(name) LIMIT(2)""); assertEquals(""TRUEPREDICATE SORT(name ASC) DISTINCT(name) LIMIT(2)"", query.getDescription()); // Descriptors should be applied in order provided RealmResults dogs = query.findAll(); assertEquals(2, dogs.size()); assertEquals(""Bella"", dogs.get(0).getName()); assertEquals(""Fido"", dogs.get(1).getName()); } // Descriptors defined by raw predicates can be realmAny with typed ones and still be applied in order @Test public void rawPredicate_mixTypedAndRawDescriptors() { realm.beginTransaction(); realm.insert(new Dog(""Milo"", 1)); realm.insert(new Dog(""Fido"", 2)); realm.insert(new Dog(""Bella"", 3)); realm.insert(new Dog(""Bella"", 3)); realm.insert(new Dog(""Bella"", 4)); realm.commitTransaction(); RealmQuery query = realm.where(Dog.class) .sort(""age"", Sort.ASCENDING) .rawPredicate(""TRUEPREDICATE SORT(name ASC) DISTINCT(name, age) LIMIT(2)"") .distinct(""age"") .limit(1); // Descriptors should be applied in order provided throughout the query assertEquals(""TRUEPREDICATE SORT(age ASC) SORT(name ASC) DISTINCT(name, age) LIMIT(2) DISTINCT(age) LIMIT(1)"", query.getDescription()); RealmResults dogs = query.findAll(); assertEquals(1, dogs.size()); assertEquals(""Bella"", dogs.get(0).getName()); assertEquals(3, dogs.get(0).getAge()); } @Test public void rawPredicate_dynamicRealmQueries() { // DynamicRealm queries hit a slightly different codepath than typed Realms, so this // is just a smoke test. populateTestRealm(); DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); try { RealmResults results = dynamicRealm .where(AllTypes.CLASS_NAME) .rawPredicate(AllTypes.FIELD_LONG + "" >= 5"") .findAll(); assertEquals(5, results.size()); } finally { dynamicRealm.close(); } } @Test public void rawPredicate_useJavaNames() { // Java Field names RealmResults results = realm.where(ClassWithValueDefinedNames.class) .rawPredicate(""field = 'Foo'"") .findAll(); assertTrue(results.isEmpty()); // Internal field name results = realm.where(ClassWithValueDefinedNames.class) .rawPredicate(""my-field-name = 'Foo'"") .findAll(); assertTrue(results.isEmpty()); // Linking Objects using the computed field results = realm.where(ClassWithValueDefinedNames.class) .rawPredicate(""parents.@count = 0"") .findAll(); assertTrue(results.isEmpty()); // Linking Objects using dynamic query with internal name for both class and property results = realm.where(ClassWithValueDefinedNames.class) .rawPredicate(""@links.my-class-name.object-link.@count = 0"") .findAll(); assertTrue(results.isEmpty()); // Linking Objects using dynamic query with internal name for class and alias for property results = realm.where(ClassWithValueDefinedNames.class) .rawPredicate(""@links.my-class-name.objectLink.@count = 0"") .findAll(); assertTrue(results.isEmpty()); // Linking Objects using dynamic query with alias for class and internal name for property results = realm.where(ClassWithValueDefinedNames.class) .rawPredicate(""@links.ClassWithValueDefinedNames.object-link.@count = 0"") .findAll(); assertTrue(results.isEmpty()); // Linking Objects using dynamic query with alias both class and property results = realm.where(ClassWithValueDefinedNames.class) .rawPredicate(""@links.ClassWithValueDefinedNames.objectLink.@count = 0"") .findAll(); assertTrue(results.isEmpty()); } @Test public void rawPredicate_argumentSubstitution() { populateTestRealm(); RealmQuery query = realm.where(AllTypes.class); query.rawPredicate(""columnString = $0 "" + ""AND columnBoolean = $1 "" + ""AND columnFloat = $2 "" + ""AND columnLong = $3"", ""test data 0"", true, 1.2345f, 0); RealmResults results = query.findAll(); assertEquals(1, results.size()); } @Test public void rawPredicate_realmObjectArgumentSubstitution() { realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName(""doggy dog""); dog.setAge(1999); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnRealmObject(dog); realm.commitTransaction(); RealmQuery query = realm.where(AllTypes.class); query.rawPredicate(""columnRealmObject = $0"", dog); RealmResults results = query.findAll(); assertEquals(1, results.size()); } @Test public void rawPredicate_embeddedObjectArgumentSubstitution() { realm.beginTransaction(); EmbeddedSimpleParent parent = realm.createObject(EmbeddedSimpleParent.class, UUID.randomUUID().toString()); parent.setChild(new EmbeddedSimpleChild()); EmbeddedSimpleChild child = parent.getChild(); realm.commitTransaction(); RealmQuery query = realm.where(EmbeddedSimpleParent.class); query.rawPredicate(""child = $0"", child); RealmResults results = query.findAll(); assertEquals(1, results.size()); } @Test public void rawPredicate_invalidRealmObjectThrows() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); realm.commitTransaction(); realm.executeTransaction(r -> allTypes.deleteFromRealm()); try { realm.where(AllTypes.class).rawPredicate(""columnRealmObject = $0"", allTypes); fail(); } catch (IllegalArgumentException e) { assertTrue(""Real message: "" + e.getMessage(), e.getMessage().contains(""RealmObject is not a valid managed object."")); } try { realm.where(AllTypes.class).rawPredicate(""columnRealmObject = $0"", new AllTypes()); fail(); } catch (IllegalArgumentException e) { assertTrue(""Real message: "" + e.getMessage(), e.getMessage().contains(""RealmObject is not a valid managed object."")); } } @Test public void rawPredicate_invalidFormatOptions() { RealmQuery query = realm.where(AllTypes.class); try { // Argument type not valid query.rawPredicate(""columnString = $0"", 42); fail(); } catch (IllegalArgumentException ignore) { } try { // Missing number of arguments query.rawPredicate(""columnString = $0 AND columnString = $1"", ""foo""); RealmLog.error(query.getDescription()); fail(); } catch (IllegalStateException ignore) { } try { // Wrong syntax for argument substitution query.rawPredicate(""columnString = %0"", ""foo""); fail(); } catch (IllegalArgumentException ignore) { } } @Test public void rawPredicate_reservedKeywords() { realm.beginTransaction(); realm.insert(new KeywordFieldNames()); realm.commitTransaction(); realm.where(KeywordFieldNames.class).rawPredicate(""desc = $0"", ""value"").findAll(); realm.where(KeywordFieldNames.class).rawPredicate(""limit = $0"", ""value"").findAll(); realm.where(KeywordFieldNames.class).rawPredicate(""sort = $0"", ""value"").findAll(); realm.where(KeywordFieldNames.class).rawPredicate(""distinct = $0"", ""value"").findAll(); } @Test public void limit() { populateTestRealm(realm, TEST_DATA_SIZE); RealmResults results = realm.where(AllTypes.class).sort(AllTypes.FIELD_LONG).limit(5).findAll(); assertEquals(5, results.size()); for (int i = 0; i < 5; i++) { assertEquals(i, results.get(i).getColumnLong()); } } @Test public void limit_withSortAndDistinct() { // The order of operators matter when using limit() // If applying sort/distinct without limit, any order will result in the same query result. realm.beginTransaction(); RealmList list = realm.createObject(AllJavaTypesUnsupportedTypes.class, -1).getFieldList(); // Root object; for (int i = 0; i < 5; i++) { AllJavaTypesUnsupportedTypes obj = realm.createObject(AllJavaTypesUnsupportedTypes.class, i); obj.setFieldLong(i); list.add(obj); } realm.commitTransaction(); RealmResults results = list.where() .sort(AllJavaTypesUnsupportedTypes.FIELD_LONG, Sort.DESCENDING) // [4, 4, 3, 3, 2, 2, 1, 1, 0, 0] .distinct(AllJavaTypesUnsupportedTypes.FIELD_LONG) // [4, 3, 2, 1, 0] .limit(2) // [4, 3] .findAll(); assertEquals(2, results.size()); assertEquals(4, results.first().getFieldLong()); assertEquals(3, results.last().getFieldLong()); results = list.where() .limit(2) // [0, 1] .distinct(AllJavaTypesUnsupportedTypes.FIELD_LONG) // [ 0, 1] .sort(AllJavaTypesUnsupportedTypes.FIELD_LONG, Sort.DESCENDING) // [1, 0] .findAll(); assertEquals(2, results.size()); assertEquals(1, results.first().getFieldLong()); assertEquals(0, results.last().getFieldLong()); results = list.where() .distinct(AllJavaTypesUnsupportedTypes.FIELD_LONG) // [ 0, 1, 2, 3, 4] .limit(2) // [0, 1] .sort(AllJavaTypesUnsupportedTypes.FIELD_LONG, Sort.DESCENDING) // [1, 0] .findAll(); assertEquals(2, results.size()); assertEquals(1, results.first().getFieldLong()); assertEquals(0, results.last().getFieldLong()); } // Checks that https://github.com/realm/realm-object-store/pull/679/files#diff-c0354faf99b53cc5d3c9e6a58ed9ae85R610 // Do not apply to Realm Java as we do not lazy-execute queries. @Test public void limit_asSubQuery() { realm.executeTransaction(r -> { for (int i = 0; i < 10; i++) { r.createObject(AllTypes.class).setColumnLong(i % 5); } }); RealmResults results = realm.where(AllTypes.class) .sort(AllTypes.FIELD_LONG, Sort.DESCENDING) .findAll() // [4, 4, 3, 3, 2, 2, 1, 1, 0, 0] .where() .distinct(AllTypes.FIELD_LONG) .findAll() // [4, 3, 2, 1, 0] .where() .limit(2) // [4, 3] .findAll(); assertEquals(2, results.size()); assertEquals(4, results.first().getColumnLong()); assertEquals(3, results.last().getColumnLong()); } @Test public void limit_invalidValuesThrows() { RealmQuery query = realm.where(AllTypes.class); try { query.limit(-1).findAll(); fail(); } catch (IllegalArgumentException ignored) { } } @Test @UiThreadTest public void findAll_runOnMainThreadAllowed() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(true) .name(""ui_realm"") .build(); Realm uiRealm = Realm.getInstance(configuration); uiRealm.where(Dog.class).findAll(); uiRealm.close(); } @Test @UiThreadTest public void findFirst_runOnMainThreadAllowed() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(true) .name(""ui_realm"") .build(); Realm uiRealm = Realm.getInstance(configuration); uiRealm.where(Dog.class).findFirst(); uiRealm.close(); } @Test @UiThreadTest public void findAll_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).findAll(); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void findFirst_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).findFirst(); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void asyncQuery_throwsWhenCallingRefresh() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.refresh(); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void count_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).count(); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void max_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).max(""age""); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void min_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).min(""age""); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void average_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).average(""age""); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void averageDecimal128_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(AllTypes.class).averageDecimal128(AllTypes.FIELD_DECIMAL128); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void maximumDate_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).maximumDate(""birthday""); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } @Test @UiThreadTest public void minimumDate_runOnMainThreadThrows() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowQueriesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.where(Dog.class).minimumDate(""birthday""); fail(""In this test queries are not allowed to run on the UI thread, so something went awry.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowQueriesOnUiThread"")); } } private void fillDictionaryTests(){ realm.executeTransaction(transactionRealm -> { DictionaryAllTypes allTypes1 = realm.createObject(DictionaryAllTypes.class); allTypes1.getColumnStringDictionary().put(""hello world1"", ""Test1""); DictionaryAllTypes allTypes2 = realm.createObject(DictionaryAllTypes.class); allTypes2.getColumnStringDictionary().put(""hello world1"", ""Test2""); DictionaryAllTypes allTypes3 = realm.createObject(DictionaryAllTypes.class); allTypes3.getColumnStringDictionary().put(""hello world2"", ""Test2""); }); } @Test public void dictionary_containsKey(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsKey(DictionaryAllTypes.FIELD_STRING_DICTIONARY, ""hello world1"").findAll(); assertEquals(2, results.size()); // We can't assert on values since we don't know the order in which the results are delivered, so better to assert that the keys are contained in the results DictionaryAllTypes results0 = results.get(0); assertNotNull(results0); assertTrue(results0.getColumnStringDictionary().containsKey(""hello world1"")); DictionaryAllTypes results1 = results.get(1); assertNotNull(results1); assertTrue(results1.getColumnStringDictionary().containsKey(""hello world1"")); } @Test public void dictionary_doesntContainKey(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsKey(DictionaryAllTypes.FIELD_STRING_DICTIONARY, ""Do I exist?"").findAll(); assertEquals(0, results.size()); } @Test public void dictionary_containsKeyNonLatin(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsKey(DictionaryAllTypes.FIELD_STRING_DICTIONARY, ""델타"").findAll(); assertEquals(0, results.size()); } @Test public void dictionary_containsValue(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsValue(DictionaryAllTypes.FIELD_STRING_DICTIONARY, ""Test2"").findAll(); assertEquals(2, results.size()); assertEquals(""Test2"", results.get(0).getColumnStringDictionary().get(""hello world1"")); assertEquals(""Test2"", results.get(1).getColumnStringDictionary().get(""hello world2"")); } @Test public void dictionary_doesntContainsValue(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsValue(DictionaryAllTypes.FIELD_STRING_DICTIONARY, ""who am I"").findAll(); assertEquals(0, results.size()); } @Test public void dictionary_containsEntry(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsEntry(DictionaryAllTypes.FIELD_STRING_DICTIONARY, new AbstractMap.SimpleImmutableEntry<>(""hello world1"", ""Test2"")).findAll(); assertEquals(1, results.size()); assertEquals(""Test2"", results.first().getColumnStringDictionary().get(""hello world1"")); } @Test public void dictionary_doesntContainsEntry(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsEntry(DictionaryAllTypes.FIELD_STRING_DICTIONARY, new AbstractMap.SimpleImmutableEntry<>(""is this"", ""real"")).findAll(); assertEquals(0, results.size()); } @Test public void dictionary_containsKeyNull(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsKey(DictionaryAllTypes.FIELD_STRING_DICTIONARY, null).findAll(); assertEquals(0, results.size()); } @Test public void dictionary_containsValueNull(){ fillDictionaryTests(); RealmResults results = realm.where(DictionaryAllTypes.class).containsValue(DictionaryAllTypes.FIELD_STRING_DICTIONARY, (Date) null).findAll(); assertEquals(0, results.size()); } @Test(expected = IllegalArgumentException.class) public void dictionary_dictionary_containsEntryNull(){ fillDictionaryTests(); realm.where(DictionaryAllTypes.class).containsEntry(DictionaryAllTypes.FIELD_STRING_DICTIONARY, null); } // Illegal Argument: Illegal Argument: Cannot sort on a collection property @Test public void dictionary_sortByDictionaryElement() { realm.executeTransaction(transactionRealm -> { transactionRealm.createObject(DictionaryAllTypes.class).getColumnStringDictionary().put(""key1"", ""value1""); transactionRealm.createObject(DictionaryAllTypes.class).getColumnStringDictionary().put(""key1"", ""value2""); transactionRealm.createObject(DictionaryAllTypes.class).getColumnStringDictionary().put(""key1"", ""value0""); transactionRealm.createObject(DictionaryAllTypes.class).getColumnStringDictionary().put(""key2"", ""value1""); } ); RealmResults results = realm.where(DictionaryAllTypes.class) .rawPredicate(""TRUEPREDICATE SORT(columnStringDictionary['key1'] ASC)"") .findAll(); // Missing keys will be sorted as 'null' values assertEquals(4, results.size()); String prevValue = null; for (DictionaryAllTypes result : results) { String value = result.getColumnStringDictionary().get(""key1""); assertTrue(prevValue == null || value != null && prevValue.compareTo(value) <= 0); prevValue = value; } } // FIXME Maybe move to QueryDescriptor or maybe even to RealmFieldType? private boolean supportDistinct(RealmFieldType type) { switch (type) { case INTEGER: case BOOLEAN: case STRING: case BINARY: case DATE: case FLOAT: case DOUBLE: case OBJECT: case DECIMAL128: case OBJECT_ID: case UUID: case LINKING_OBJECTS: case MIXED: return true; case LIST: case INTEGER_LIST: case BOOLEAN_LIST: case STRING_LIST: case BINARY_LIST: case DATE_LIST: case FLOAT_LIST: case DOUBLE_LIST: case DECIMAL128_LIST: case OBJECT_ID_LIST: case UUID_LIST: case MIXED_LIST: case STRING_TO_MIXED_MAP: case STRING_TO_BOOLEAN_MAP: case STRING_TO_STRING_MAP: case STRING_TO_INTEGER_MAP: case STRING_TO_FLOAT_MAP: case STRING_TO_DOUBLE_MAP: case STRING_TO_BINARY_MAP: case STRING_TO_DATE_MAP: case STRING_TO_OBJECT_ID_MAP: case STRING_TO_UUID_MAP: case STRING_TO_DECIMAL128_MAP: case STRING_TO_LINK_MAP: case BOOLEAN_SET: case STRING_SET: case INTEGER_SET: case FLOAT_SET: case DOUBLE_SET: case BINARY_SET: case DATE_SET: case DECIMAL128_SET: case OBJECT_ID_SET: case UUID_SET: case LINK_SET: case MIXED_SET: return false; case TYPED_LINK: } // Should never reach here as the above switch is exhaustive throw new UnsupportedOperationException(""Unhandled realm field type "" + type); } // Manual distinct method for verification. Uses field value's equals. @NotNull private Set> distinct(RealmResults all, Object... fields) { Set> values = new HashSet(); // Parsed hierarchical field accessors List [MASK] = new ArrayList<>(); for (Object field : fields) { [MASK] .add(((String) field).split(""\\."")); } for (DynamicRealmObject object : all) { List elements = new ArrayList<>(fields.length); for (String[] split : [MASK] ) { int i = 0; while (i < split.length - 1) { object = object.get(split[i]); i++; } String fieldName = split[i]; if (!object.isNull(fieldName)) { Object e = object.get(fieldName); // Need to convert byte arrays to list to detect duplicates when inserting to values if (e instanceof byte[]) { elements.add(convertBytesToList((byte[]) e)); } else { elements.add(e); } } else { elements.add(null); } } values.add(elements); } return values; } private static List convertBytesToList(byte[] bytes) { final List list = new ArrayList<>(); for (byte b : bytes) { list.add(b); } return list; } } ","fieldAccessors " "/* * Copyright 2010 Mario Zechner (contact@badlogicgames.com), Nathan Sweet (admin@esotericsoftware.com) * * Modified by Elijah Cornell * 2013.01 Modified by Jaroslaw Wisniewski * 2014.04 Modified by davebaol * * Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an ""AS IS"" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package com.badlogic.gdx.backends.android; import android.opengl.GLSurfaceView.EGLConfigChooser; import android.util.Log; import android.view.SurfaceHolder; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceView20; import com.badlogic.gdx.backends.android.surfaceview.ResolutionStrategy; import com.badlogic.gdx.utils.GdxRuntimeException; /** A subclass of {@link AndroidGraphics} specialized for live wallpaper applications. * * @author mzechner */ public final class AndroidGraphicsLiveWallpaper extends AndroidGraphics { public AndroidGraphicsLiveWallpaper (AndroidLiveWallpaper lwp, AndroidApplicationConfiguration config, ResolutionStrategy [MASK] ) { super(lwp, config, [MASK] , false); } // jw: I replaced GL..SurfaceViewLW classes with their original counterparts, if it will work // on known devices, on opengl 1.0 and 2.0, and all possible SDK versions.. You can remove // GL..SurfaceViewLW family of classes completely (there is no use for them). // -> specific for live wallpapers // jw: synchronized access to current wallpaper surface holder SurfaceHolder getSurfaceHolder () { synchronized (((AndroidLiveWallpaper)app).service.sync) { return ((AndroidLiveWallpaper)app).service.getSurfaceHolder(); } } // <- specific for live wallpapers // Grabbed from AndroidGraphics superclass and modified to override // getHolder in created GLSurfaceView20 instances @Override protected GLSurfaceView20 createGLSurfaceView (AndroidApplicationBase application, final ResolutionStrategy [MASK] ) { if (!checkGL20()) throw new GdxRuntimeException(""libGDX requires OpenGL ES 2.0""); EGLConfigChooser configChooser = getEglConfigChooser(); GLSurfaceView20 view = new GLSurfaceView20(application.getContext(), [MASK] ) { @Override public SurfaceHolder getHolder () { return getSurfaceHolder(); } }; if (configChooser != null) view.setEGLConfigChooser(configChooser); else view.setEGLConfigChooser(config.r, config.g, config.b, config.a, config.depth, config.stencil); view.setRenderer(this); return view; } // kill the GLThread managed by GLSurfaceView (only for GLSurfaceView because GLSurffaceViewCupcake stops thread in // onPause events - which is not as easy and safe for GLSurfaceView) public void onDestroyGLSurfaceView () { if (view != null) { try { // onDetachedFromWindow stops GLThread by calling mGLThread.requestExitAndWait() view.onDetachedFromWindow(); if (AndroidLiveWallpaperService.DEBUG) Log.d(AndroidLiveWallpaperService.TAG, "" > AndroidLiveWallpaper - onDestroy() stopped GLThread managed by GLSurfaceView""); } catch (Throwable t) { // error while scheduling exit of GLThread, GLThread will remain live and wallpaper service // wouldn't be able to shutdown completely Log.e(AndroidLiveWallpaperService.TAG, ""failed to destroy GLSurfaceView's thread! GLSurfaceView.onDetachedFromWindow impl changed since API lvl 16!""); t.printStackTrace(); } } } @Override void resume () { synchronized (synch) { running = true; resume = true; while (resume) { try { requestRendering(); synch.wait(); } catch (InterruptedException ignored) { Gdx.app.log(""AndroidGraphics"", ""waiting for resume synchronization failed!""); } } } } @Override public void onDrawFrame (javax.microedition.khronos.opengles.GL10 gl) { long time = System.nanoTime(); // After pause deltaTime can have somewhat huge value that destabilizes the mean, so let's cut it off if (!resume) { deltaTime = (time - lastFrameTime) / 1000000000.0f; } else { deltaTime = 0; } lastFrameTime = time; boolean lrunning = false; boolean lpause = false; boolean ldestroy = false; boolean lresume = false; synchronized (synch) { lrunning = running; lpause = pause; ldestroy = destroy; lresume = resume; if (resume) { resume = false; synch.notifyAll(); } if (pause) { pause = false; synch.notifyAll(); } if (destroy) { destroy = false; synch.notifyAll(); } } if (lresume) { // ((AndroidAudio)app.getAudio()).resume(); // jw: moved to AndroidLiveWallpaper.onResume app.getApplicationListener().resume(); Gdx.app.log(""AndroidGraphics"", ""resumed""); } // HACK: added null check to handle set wallpaper from preview null // error in renderer // jw: this hack is not working always, renderer ends with error for some devices - because of uninitialized gl context // jw: now it shouldn't be necessary - after wallpaper backend refactoring:) if (lrunning) { // jw: changed synchronized (app.getRunnables()) { app.getExecutedRunnables().clear(); app.getExecutedRunnables().addAll(app.getRunnables()); app.getRunnables().clear(); for (int i = 0; i < app.getExecutedRunnables().size; i++) { try { app.getExecutedRunnables().get(i).run(); } catch (Throwable t) { t.printStackTrace(); } } } /* * synchronized (app.runnables) { for (int i = 0; i < app.runnables.size; i++) { app.runnables.get(i).run(); } * app.runnables.clear(); } */ app.getInput().processEvents(); frameId++; app.getApplicationListener().render(); } // jw: never called on lvp, why? see description in AndroidLiveWallpaper.onPause if (lpause) { app.getApplicationListener().pause(); // ((AndroidAudio)app.getAudio()).pause(); jw: moved to AndroidLiveWallpaper.onPause Gdx.app.log(""AndroidGraphics"", ""paused""); } // jw: never called on lwp, why? see description in AndroidLiveWallpaper.onPause if (ldestroy) { app.getApplicationListener().dispose(); // ((AndroidAudio)app.getAudio()).dispose(); jw: moved to AndroidLiveWallpaper.onDestroy Gdx.app.log(""AndroidGraphics"", ""destroyed""); } if (time - frameStart > 1000000000) { fps = frames; frames = 0; frameStart = time; } frames++; } @Override protected void logManagedCachesStatus () { // to prevent creating too many string buffers in live wallpapers if (AndroidLiveWallpaperService.DEBUG) { super.logManagedCachesStatus(); } } } ","resolutionStrategy " "// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.cpp; import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.RuleErrorConsumer; import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.regex.Pattern; import net.starlark.java.eval.StarlarkThread; /** Handles creation of CppCompileAction used to compile linkstamp sources. */ public final class CppLinkstampCompileHelper { /** Creates {@link CppCompileAction} to compile linkstamp source. */ public static CppCompileAction createLinkstampCompileAction( RuleErrorConsumer ruleErrorConsumer, ActionConstructionContext actionConstructionContext, BuildConfigurationValue configuration, Artifact sourceFile, Artifact outputFile, NestedSet compilationInputs, NestedSet nonCodeInputs, NestedSet inputsForInvalidation, ImmutableList buildInfoHeaderArtifacts, Iterable additionalLinkstampDefines, CcToolchainProvider ccToolchainProvider, boolean codeCoverageEnabled, CppConfiguration cppConfiguration, String [MASK] , FeatureConfiguration featureConfiguration, boolean needsPic, String labelReplacement, String outputReplacement, CppSemantics semantics) throws RuleErrorException, InterruptedException { CppCompileActionBuilder builder = new CppCompileActionBuilder( actionConstructionContext, ccToolchainProvider, configuration, semantics) .addMandatoryInputs(compilationInputs) .setVariables( getVariables( ((RuleContext) actionConstructionContext).getStarlarkThread(), ruleErrorConsumer, sourceFile, outputFile, labelReplacement, outputReplacement, additionalLinkstampDefines, buildInfoHeaderArtifacts, featureConfiguration, configuration.getOptions(), cppConfiguration, ccToolchainProvider, needsPic, [MASK] , codeCoverageEnabled, semantics)) .setFeatureConfiguration(featureConfiguration) .setSourceFile(sourceFile) .setOutputs(outputFile, /* dotdFile= */ null, /* diagnosticsFile= */ null) .setCacheKeyInputs(inputsForInvalidation) .setBuildInfoHeaderArtifacts(buildInfoHeaderArtifacts) .addMandatoryInputs(nonCodeInputs) .setShareable(true) .setShouldScanIncludes(false) .setActionName(CppActionNames.LINKSTAMP_COMPILE); semantics.finalizeCompileActionBuilder( configuration, featureConfiguration, builder, ruleErrorConsumer); return builder.buildOrThrowIllegalStateException(); } private static Iterable computeAllLinkstampDefines( String labelReplacement, String outputReplacement, Iterable additionalLinkstampDefines, CcToolchainProvider ccToolchainProvider, String [MASK] , boolean codeCoverageEnabled) { String labelPattern = Pattern.quote(""${LABEL}""); String outputPathPattern = Pattern.quote(""${OUTPUT_PATH}""); ImmutableList.Builder defines = ImmutableList.builder() .add(""GPLATFORM=\"""" + ccToolchainProvider.getToolchainIdentifier() + ""\"""") .add(""BUILD_COVERAGE_ENABLED="" + (codeCoverageEnabled ? ""1"" : ""0"")) // G3_TARGET_NAME is a C string literal that normally contain the label of the target // being linked. However, they are set differently when using shared native deps. In // that case, a single .so file is shared by multiple targets, and its contents cannot // depend on which target(s) were specified on the command line. So in that case we // have to use the (obscure) name of the .so file instead, or more precisely the path of // the .so file relative to the workspace root. .add(""G3_TARGET_NAME=\""${LABEL}\"""") // G3_BUILD_TARGET is a C string literal containing the output of this // link. (An undocumented and untested invariant is that G3_BUILD_TARGET is the // location of the executable, either absolutely, or relative to the directory part of // BUILD_INFO.) .add(""G3_BUILD_TARGET=\""${OUTPUT_PATH}\"""") .addAll(additionalLinkstampDefines); if ( [MASK] != null) { defines.add(CppConfiguration.FDO_STAMP_MACRO + ""=\"""" + [MASK] + ""\""""); } return Iterables.transform( defines.build(), define -> define .replaceAll(labelPattern, labelReplacement) .replaceAll(outputPathPattern, outputReplacement)); } private static CcToolchainVariables getVariables( StarlarkThread thread, RuleErrorConsumer ruleErrorConsumer, Artifact sourceFile, Artifact outputFile, String labelReplacement, String outputReplacement, Iterable additionalLinkstampDefines, ImmutableList buildInfoHeaderArtifacts, FeatureConfiguration featureConfiguration, BuildOptions buildOptions, CppConfiguration cppConfiguration, CcToolchainProvider ccToolchainProvider, boolean needsPic, String [MASK] , boolean codeCoverageEnabled, CppSemantics semantics) throws RuleErrorException, InterruptedException { // TODO(b/34761650): Remove all this hardcoding by separating a full blown compile action. Preconditions.checkArgument( featureConfiguration.actionIsConfigured(CppActionNames.LINKSTAMP_COMPILE)); return CompileBuildVariables.setupVariablesOrReportRuleError( thread, ruleErrorConsumer, featureConfiguration, ccToolchainProvider, buildOptions, cppConfiguration, sourceFile.getExecPathString(), outputFile.getExecPathString(), /* gcnoFile= */ null, /* isUsingFission= */ false, /* dwoFile= */ null, /* ltoIndexingFile= */ null, buildInfoHeaderArtifacts.stream() .map(Artifact::getExecPathString) .collect(toImmutableList()), CcCompilationHelper.getCoptsFromOptions( cppConfiguration, semantics, sourceFile.getExecPathString()), /* cppModuleMap= */ null, needsPic, [MASK] , /* dotdFileExecPath= */ null, /* diagnosticsFileExecPath= */ null, /* variablesExtensions= */ ImmutableList.of(), /* additionalBuildVariables= */ ImmutableMap.of(), /* directModuleMaps= */ ImmutableList.of(), /* includeDirs= */ NestedSetBuilder.create(Order.STABLE_ORDER, PathFragment.create(""."")), /* quoteIncludeDirs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), /* systemIncludeDirs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), /* frameworkIncludeDirs= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER), computeAllLinkstampDefines( labelReplacement, outputReplacement, additionalLinkstampDefines, ccToolchainProvider, [MASK] , codeCoverageEnabled), /* localDefines= */ ImmutableList.of()); } private CppLinkstampCompileHelper() {} } ","fdoBuildStamp " "// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android.aapt2; import com.android.SdkConstants; import com.android.builder.core.VariantTypeImpl; import com.android.repository.Revision; import com.android.resources.ResourceFolderType; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.devtools.build.android.AaptCommandBuilder; import com.google.devtools.build.android.AndroidDataSerializer; import com.google.devtools.build.android.DataResourceXml; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.FullyQualifiedName.Factory; import com.google.devtools.build.android.FullyQualifiedName.Qualifiers; import com.google.devtools.build.android.FullyQualifiedName.VirtualType; import com.google.devtools.build.android.ResourceProcessorBusyBox; import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.xml.Namespaces; import com.google.devtools.build.android.xml.ResourcesAttribute; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; /** Invokes aapt2 to compile resources. */ public class ResourceCompiler { /** Types of compiled resources. */ public enum CompiledType { NORMAL(null), GENERATED(""generated""), DEFAULT(""default""); private final String prefix; CompiledType(String prefix) { this.prefix = prefix; } boolean prefixes(String filename) { return prefix != null && filename.startsWith(prefix); } public String asPrefix() { return prefix; } public String asComment() { return prefix; } public String prefix(String path) { return prefix + ""/"" + path; } } public static CompiledType getCompiledType(String fileName) { return Arrays.stream(CompiledType.values()) .filter(t -> t.prefixes(fileName)) .findFirst() .orElse(CompiledType.NORMAL); } static class CompileError extends Aapt2Exception { protected CompileError(Throwable e) { super(e); } private CompileError() { super(); } public static CompileError of(List compilationErrors) { final CompileError compileError = new CompileError(); compilationErrors.forEach(compileError::addSuppressed); return compileError; } } private static final Logger logger = Logger.getLogger(ResourceCompiler.class.getName()); // https://android-review.googlesource.com/c/platform/frameworks/base/+/1202901 public static final boolean USE_VISIBILITY_FROM_AAPT2 = ResourceProcessorBusyBox.getProperty(""use_visibility_from_aapt2""); private final CompilingVisitor compilingVisitor; private static class CompileTask implements Callable> { private final Path file; private final Path compiledResourcesOut; private final Path aapt2; private final Revision buildToolsVersion; private final Optional generatedResourcesOut; private CompileTask( Path file, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion, Optional generatedResourcesOut) { this.file = file; this.compiledResourcesOut = compiledResourcesOut; this.aapt2 = aapt2; this.buildToolsVersion = buildToolsVersion; this.generatedResourcesOut = generatedResourcesOut; } @Override public List call() throws Exception { final String directoryName = file.getParent().getFileName().toString(); final Qualifiers qualifiers = Qualifiers.parseFrom(directoryName); final ResourceFolderType resourceFolderType = qualifiers.asFolderType(); if (resourceFolderType == null) { throw new CompileError( new IllegalArgumentException(""Unexpected resource folder for file: "" + file)); } final String filename = interpolateAapt2Filename(resourceFolderType, file); final List results = new ArrayList<>(); if (resourceFolderType.equals(ResourceFolderType.VALUES) || (resourceFolderType.equals(ResourceFolderType.RAW) && file.getFileName().toString().endsWith("".xml""))) { extractAttributes(directoryName, filename, results); } if (qualifiers.containDefaultLocale() && resourceFolderType.equals(ResourceFolderType.VALUES)) { compile( directoryName, filename, results, compiledResourcesOut.resolve(CompiledType.DEFAULT.asPrefix()), file, false); // aapt2 only generates pseudo locales for the default locale. // TODO(b/149251235): omit this file if the output is identical to the default config above. generatedResourcesOut.ifPresent( out -> compile(directoryName, filename, results, out, file, true)); } else { compile(directoryName, filename, results, compiledResourcesOut, file, false); } return results; } static String interpolateAapt2Filename(ResourceFolderType resourceFolderType, Path file) { // res//foo.bar -> foo.bar String filename = file.getFileName().toString(); if (!resourceFolderType.equals(ResourceFolderType.VALUES)) { return filename; } int periodIndex = filename.indexOf('.'); // res/values/foo -> foo.arsc if (periodIndex == -1) { return filename + "".arsc""; } // res/values/foo.bar.baz -> throw error. if (filename.lastIndexOf('.') != periodIndex) { throw new CompileError( new IllegalArgumentException( ""aapt2 does not support compiling resource xmls with multiple periods in the "" + ""filename: "" + file)); } // res/values/foo.xml -> foo.arsc return filename.substring(0, periodIndex) + "".arsc""; } private void compile( String type, String filename, List results, Path compileOutRoot, Path file, boolean generatePseudoLocale) { try { Path destination = CompilingVisitor.destinationPath(file, compileOutRoot); final Path compiledResourcePath = destination.resolve(type + ""_"" + filename + "".flat""); logger.fine( new AaptCommandBuilder(aapt2) .forBuildToolsVersion(buildToolsVersion) .forVariantType(VariantTypeImpl.LIBRARY) .add(""compile"") .add(""-v"") .add(""--legacy"") .when(USE_VISIBILITY_FROM_AAPT2) .thenAdd(""--preserve-visibility-of-styleables"") .when(generatePseudoLocale) .thenAdd(""--pseudo-localize"") .add(""-o"", destination.toString()) .add(file.toString()) .execute(""Compiling "" + file)); Preconditions.checkArgument( Files.exists(compiledResourcePath), ""%s does not exist after aapt2 ran."", compiledResourcePath); results.add(compiledResourcePath); } catch (IOException e) { throw new CompileError(e); } } private void extractAttributes(String type, String filename, List results) throws Exception { XMLEventReader xmlEventReader = null; try { // aapt2 compile strips out namespaces and attributes from the resources tag. // Read them here separately and package them with the other flat files. xmlEventReader = XMLInputFactory.newInstance() .createXMLEventReader(new FileInputStream(file.toString())); // Iterate through the XML until we find a start element. // This should mimic xmlEventReader.nextTag() except that it also skips DTD elements. StartElement rootElement = null; while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.getEventType() != XMLStreamConstants.COMMENT && event.getEventType() != XMLStreamConstants.DTD && event.getEventType() != XMLStreamConstants.PROCESSING_INSTRUCTION && event.getEventType() != XMLStreamConstants.SPACE && event.getEventType() != XMLStreamConstants.START_DOCUMENT) { // If the event should not be skipped, try parsing it as a start element here. // If the event is not a start element, an appropriate exception will be thrown. rootElement = event.asStartElement(); break; } } if (rootElement == null) { throw new Exception(""No start element found in resource XML file: "" + file.toString()); } Iterator attributeIterator = XmlResourceValues.iterateAttributesFrom(rootElement); if (attributeIterator.hasNext()) { results.add(createAttributesProto(type, filename, attributeIterator)); } } finally { if (xmlEventReader != null) { xmlEventReader.close(); } } } private Path createAttributesProto( String type, String filename, Iterator attributeIterator) throws IOException { AndroidDataSerializer serializer = AndroidDataSerializer.create(); final Path resourcesAttributesPath = CompilingVisitor.destinationPath(file, compiledResourcesOut) .resolve(type + ""_"" + filename + CompiledResources.ATTRIBUTES_FILE_EXTENSION); Preconditions.checkArgument( !Files.exists(resourcesAttributesPath), ""%s was already created for another resource."", resourcesAttributesPath); while (attributeIterator.hasNext()) { Attribute attribute = attributeIterator.next(); String namespaceUri = attribute.getName().getNamespaceURI(); String localPart = attribute.getName().getLocalPart(); String prefix = attribute.getName().getPrefix(); QName qName = new QName(namespaceUri, localPart, prefix); Namespaces namespaces = Namespaces.from(qName); String attributeName = namespaceUri.isEmpty() ? localPart : prefix + "":"" + localPart; final String[] dirNameAndQualifiers = type.split(SdkConstants.RES_QUALIFIER_SEP); Factory fqnFactory = Factory.fromDirectoryName(dirNameAndQualifiers); FullyQualifiedName fqn = fqnFactory.create(VirtualType.RESOURCES_ATTRIBUTE, qName.toString()); ResourcesAttribute resourceAttribute = ResourcesAttribute.of(fqn, attributeName, attribute.getValue()); DataResourceXml resource = DataResourceXml.createWithNamespaces(file, resourceAttribute, namespaces); serializer.queueForSerialization(fqn, resource); } serializer.flushTo(resourcesAttributesPath); return resourcesAttributesPath; } @Override public String toString() { return ""ResourceCompiler.CompileTask("" + file + "")""; } } private static class CompilingVisitor extends SimpleFileVisitor { private final ListeningExecutorService executorService; private final Path compiledResourcesOut; private final Set pathToProcessed = new LinkedHashSet<>(); private final Path aapt2; private final Revision buildToolsVersion; private final Optional generatedResourcesOut; public CompilingVisitor( ListeningExecutorService executorService, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion, Optional generatedResourcesOut) { this.executorService = executorService; this.compiledResourcesOut = compiledResourcesOut; this.aapt2 = aapt2; this.buildToolsVersion = buildToolsVersion; this.generatedResourcesOut = generatedResourcesOut; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { // Ignore directories and ""hidden"" files that start with ., ends with .tmp or .params files. final String fileName = file.getFileName().toString(); if (!Files.isDirectory(file) && !fileName.startsWith(""."") && !fileName.endsWith("".tmp"") && !fileName.endsWith("".params"")) { pathToProcessed.add(file); } return super.visitFile(file, attrs); } public static Path destinationPath(Path file, Path compiledResourcesOut) { // Creates a relative output path based on the input path under the // compiledResources path. try { return Files.createDirectories( compiledResourcesOut.resolve( (file.isAbsolute() ? file.getRoot().relativize(file) : file) .getParent() .getParent())); } catch (IOException e) { throw new CompileError(e); } } List getCompiledArtifacts() { generatedResourcesOut.ifPresent( out -> { try { Files.createDirectories(out); } catch (IOException e) { throw new CompileError(e); } }); List>> [MASK] = new ArrayList<>(); for (Path uncompiled : pathToProcessed) { [MASK] .add( executorService.submit( new CompileTask( uncompiled, compiledResourcesOut, aapt2, buildToolsVersion, generatedResourcesOut))); } ImmutableList.Builder compiled = ImmutableList.builder(); ImmutableList.Builder generated = ImmutableList.builder(); List compilationErrors = new ArrayList<>(); for (ListenableFuture> task : [MASK] ) { try { // Split the generated and non-generated resources into different collections. // This allows the generated files to be placed first in the compile order, // ensuring that the generated locale (en-XA and ar-XB) can be overwritten by // user provided versions for those locales, as aapt2 will take the last value for // a configuration when linking. task.get() .forEach( path -> { if (generatedResourcesOut.map(path::startsWith).orElse(false)) { generated.add(path); } else { compiled.add(path); } }); } catch (InterruptedException | ExecutionException e) { compilationErrors.add(e.getCause() != null ? e.getCause() : e); } } generated.addAll(compiled.build()); if (compilationErrors.isEmpty()) { // ensure that the generated files are before the normal files. return ImmutableList.sortedCopyOf(generated.build()); } throw CompileError.of(compilationErrors); } } /** Creates a new {@link ResourceCompiler}. */ public static ResourceCompiler create( ListeningExecutorService executorService, Path compiledResources, Path aapt2, Revision buildToolsVersion, boolean generatePseudoLocale) { return new ResourceCompiler( new CompilingVisitor( executorService, compiledResources, aapt2, buildToolsVersion, generatePseudoLocale ? Optional.of(compiledResources.resolve(CompiledType.GENERATED.asPrefix())) : Optional.empty())); } private ResourceCompiler(CompilingVisitor compilingVisitor) { this.compilingVisitor = compilingVisitor; } /** Adds a task to compile the directory using aapt2. */ public void queueDirectoryForCompilation(Path resource) throws IOException { Files.walkFileTree(resource, compilingVisitor); } /** Returns all paths of the aapt2 compiled resources. */ public List getCompiledArtifacts() throws InterruptedException, ExecutionException { return compilingVisitor.getCompiledArtifacts(); } } ","tasks " "/* * Copyright 1999-2019 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.fastjson.parser; import com.alibaba.fastjson.*; import com.alibaba.fastjson.parser.deserializer.*; import com.alibaba.fastjson.serializer.*; import com.alibaba.fastjson.util.TypeUtils; import java.io.Closeable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import static com.alibaba.fastjson.parser.JSONLexer.EOI; import static com.alibaba.fastjson.parser.JSONToken.*; /** * @author wenshao[szujobs@hotmail.com] */ public class DefaultJSONParser implements Closeable { public final Object input; public final SymbolTable symbolTable; protected ParserConfig config; private final static Set> primitiveClasses = new HashSet>(); private String dateFormatPattern = JSON.DEFFAULT_DATE_FORMAT; private DateFormat dateFormat; public final JSONLexer lexer; protected ParseContext context; private ParseContext[] contextArray; private int contextArrayIndex = 0; private List resolveTaskList; public final static int NONE = 0; public final static int NeedToResolve = 1; public final static int TypeNameRedirect = 2; public int resolveStatus = NONE; private List extraTypeProviders = null; private List extraProcessors = null; protected FieldTypeResolver fieldTypeResolver = null; private int objectKeyLevel = 0; private boolean autoTypeEnable; private String[] autoTypeAccept = null; protected transient BeanContext lastBeanContext; static { Class[] classes = new Class[] { boolean.class, byte.class, short.class, int.class, long.class, float.class, double.class, Boolean.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, BigInteger.class, BigDecimal.class, String.class }; primitiveClasses.addAll(Arrays.asList(classes)); } public String getDateFomartPattern() { return dateFormatPattern; } public DateFormat getDateFormat() { if (dateFormat == null) { dateFormat = new SimpleDateFormat(dateFormatPattern, lexer.getLocale()); dateFormat.setTimeZone(lexer.getTimeZone()); } return dateFormat; } public void setDateFormat(String dateFormat) { this.dateFormatPattern = dateFormat; this.dateFormat = null; } /** * @deprecated * @see setDateFormat */ public void setDateFomrat(DateFormat dateFormat) { this.setDateFormat(dateFormat); } public void setDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; } public DefaultJSONParser(String input){ this(input, ParserConfig.getGlobalInstance(), JSON.DEFAULT_PARSER_FEATURE); } public DefaultJSONParser(final String input, final ParserConfig config){ this(input, new JSONScanner(input, JSON.DEFAULT_PARSER_FEATURE), config); } public DefaultJSONParser(final String input, final ParserConfig config, int features){ this(input, new JSONScanner(input, features), config); } public DefaultJSONParser(final char[] input, int length, final ParserConfig config, int features){ this(input, new JSONScanner(input, length, features), config); } public DefaultJSONParser(final JSONLexer lexer){ this(lexer, ParserConfig.getGlobalInstance()); } public DefaultJSONParser(final JSONLexer lexer, final ParserConfig config){ this(null, lexer, config); } public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){ this.lexer = lexer; this.input = input; this.config = config; this.symbolTable = config.symbolTable; int ch = lexer.getCurrent(); if (ch == '{') { lexer.next(); ((JSONLexerBase) lexer).token = JSONToken.LBRACE; } else if (ch == '[') { lexer.next(); ((JSONLexerBase) lexer).token = JSONToken.LBRACKET; } else { lexer.nextToken(); // prime the pump } } public SymbolTable getSymbolTable() { return symbolTable; } public String getInput() { if (input instanceof char[]) { return new String((char[]) input); } return input.toString(); } @SuppressWarnings({ ""unchecked"", ""rawtypes"" }) public final Object parseObject(final Map object, Object fieldName) { final JSONLexer lexer = this.lexer; if (lexer.token() == JSONToken.NULL) { lexer.nextToken(); return null; } if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(); return object; } if (lexer.token() == JSONToken.LITERAL_STRING && lexer.stringVal().length() == 0) { lexer.nextToken(); return object; } if (lexer.token() != JSONToken.LBRACE && lexer.token() != JSONToken.COMMA) { throw new JSONException(""syntax error, expect {, actual "" + lexer.tokenName() + "", "" + lexer.info()); } ParseContext context = this.context; try { boolean isJsonObjectMap = object instanceof JSONObject; Map map = isJsonObjectMap ? ((JSONObject) object).getInnerMap() : object; boolean setContextFlag = false; for (;;) { lexer.skipWhitespace(); char ch = lexer.getCurrent(); if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { while (ch == ',') { lexer.next(); lexer.skipWhitespace(); ch = lexer.getCurrent(); } } boolean isObjectKey = false; Object key; if (ch == '""') { key = lexer.scanSymbol(symbolTable, '""'); lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException(""expect ':' at "" + lexer.pos() + "", name "" + key); } } else if (ch == '}') { lexer.next(); lexer.resetStringPosition(); lexer.nextToken(); if (!setContextFlag) { if (this.context != null && fieldName == this.context.fieldName && object == this.context.object) { context = this.context; } else { ParseContext contextR = setContext(object, fieldName); if (context == null) { context = contextR; } setContextFlag = true; } } return object; } else if (ch == '\'') { if (!lexer.isEnabled(Feature.AllowSingleQuotes)) { throw new JSONException(""syntax error""); } key = lexer.scanSymbol(symbolTable, '\''); lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException(""expect ':' at "" + lexer.pos()); } } else if (ch == EOI) { throw new JSONException(""syntax error""); } else if (ch == ',') { throw new JSONException(""syntax error""); } else if ((ch >= '0' && ch <= '9') || ch == '-') { lexer.resetStringPosition(); lexer.scanNumber(); try { if (lexer.token() == JSONToken.LITERAL_INT) { key = lexer.integerValue(); } else { key = lexer.decimalValue(true); } if (lexer.isEnabled(Feature.NonStringKeyAsString) || isJsonObjectMap) { key = key.toString(); } } catch (NumberFormatException e) { throw new JSONException(""parse number key error"" + lexer.info()); } ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException(""parse number key error"" + lexer.info()); } } else if (ch == '{' || ch == '[') { if (objectKeyLevel++ > 512) { throw new JSONException(""object key level > 512""); } lexer.nextToken(); key = parse(); isObjectKey = true; } else { if (!lexer.isEnabled(Feature.AllowUnQuotedFieldNames)) { throw new JSONException(""syntax error""); } key = lexer.scanSymbolUnQuoted(symbolTable); lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException(""expect ':' at "" + lexer.pos() + "", actual "" + ch); } } if (!isObjectKey) { lexer.next(); lexer.skipWhitespace(); } ch = lexer.getCurrent(); lexer.resetStringPosition(); if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) { String typeName = lexer.scanSymbol(symbolTable, '""'); if (lexer.isEnabled(Feature.IgnoreAutoType)) { continue; } Class clazz = null; if (object != null && object.getClass().getName().equals(typeName)) { clazz = object.getClass(); } else if (""java.util.HashMap"".equals(typeName)) { clazz = java.util.HashMap.class; } else if (""java.util.LinkedHashMap"".equals(typeName)) { clazz = java.util.LinkedHashMap.class; } else { boolean allDigits = true; for (int i = 0; i < typeName.length(); ++i) { char c = typeName.charAt(i); if (c < '0' || c > '9') { allDigits = false; break; } } if (!allDigits) { clazz = config.checkAutoType(typeName, null, lexer.getFeatures()); } } if (clazz == null) { map.put(JSON.DEFAULT_TYPE_KEY, typeName); continue; } lexer.nextToken(JSONToken.COMMA); if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(JSONToken.COMMA); try { Object instance = null; ObjectDeserializer deserializer = this.config.getDeserializer(clazz); if (deserializer instanceof JavaBeanDeserializer) { instance = TypeUtils.cast(object, clazz, this.config); } if (instance == null) { if (clazz == Cloneable.class) { instance = new HashMap(); } else if (""java.util.Collections$EmptyMap"".equals(typeName)) { instance = Collections.emptyMap(); } else if (""java.util.Collections$UnmodifiableMap"".equals(typeName)) { instance = Collections.unmodifiableMap(new HashMap()); } else { instance = clazz.newInstance(); } } return instance; } catch (Exception e) { throw new JSONException(""create instance error"", e); } } this.setResolveStatus(TypeNameRedirect); if (this.context != null && fieldName != null && !(fieldName instanceof Integer) && !(this.context.fieldName instanceof Integer)) { this.popContext(); } if (object.size() > 0) { Object newObj = TypeUtils.cast(object, clazz, this.config); this.setResolveStatus(NONE); this.parseObject(newObj); return newObj; } ObjectDeserializer deserializer = config.getDeserializer(clazz); Class deserClass = deserializer.getClass(); if (JavaBeanDeserializer.class.isAssignableFrom(deserClass) && deserClass != JavaBeanDeserializer.class && deserClass != ThrowableDeserializer.class) { this.setResolveStatus(NONE); } else if (deserializer instanceof MapDeserializer) { this.setResolveStatus(NONE); } Object obj = deserializer.deserialze(this, clazz, fieldName); return obj; } if (key == ""$ref"" && context != null && (object == null || object.size() == 0) && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) { lexer.nextToken(JSONToken.LITERAL_STRING); if (lexer.token() == JSONToken.LITERAL_STRING) { String ref = lexer.stringVal(); lexer.nextToken(JSONToken.RBRACE); if (lexer.token() == JSONToken.COMMA) { map.put(key, ref); continue; } Object refValue = null; if (""@"".equals(ref)) { if (this.context != null) { ParseContext thisContext = this.context; Object thisObj = thisContext.object; if (thisObj instanceof Object[] || thisObj instanceof Collection) { refValue = thisObj; } else if (thisContext.parent != null) { refValue = thisContext.parent.object; } } } else if ("".."".equals(ref)) { if (context.object != null) { refValue = context.object; } else { addResolveTask(new ResolveTask(context, ref)); setResolveStatus(DefaultJSONParser.NeedToResolve); } } else if (""$"".equals(ref)) { ParseContext rootContext = context; while (rootContext.parent != null) { rootContext = rootContext.parent; } if (rootContext.object != null) { refValue = rootContext.object; } else { addResolveTask(new ResolveTask(rootContext, ref)); setResolveStatus(DefaultJSONParser.NeedToResolve); } } else { JSONPath jsonpath = JSONPath.compile(ref); if (jsonpath.isRef()) { addResolveTask(new ResolveTask(context, ref)); setResolveStatus(DefaultJSONParser.NeedToResolve); } else { refValue = new JSONObject() .fluentPut(""$ref"", ref); } } if (lexer.token() != JSONToken.RBRACE) { throw new JSONException(""syntax error, "" + lexer.info()); } lexer.nextToken(JSONToken.COMMA); return refValue; } else { throw new JSONException(""illegal ref, "" + JSONToken.name(lexer.token())); } } if (!setContextFlag) { if (this.context != null && fieldName == this.context.fieldName && object == this.context.object) { context = this.context; } else { ParseContext contextR = setContext(object, fieldName); if (context == null) { context = contextR; } setContextFlag = true; } } if (object.getClass() == JSONObject.class) { if (key == null) { key = ""null""; } } Object value; if (ch == '""') { lexer.scanString(); String strValue = lexer.stringVal(); value = strValue; if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) { JSONScanner iso8601Lexer = new JSONScanner(strValue); if (iso8601Lexer.scanISO8601DateIfMatch()) { value = iso8601Lexer.getCalendar().getTime(); } iso8601Lexer.close(); } map.put(key, value); } else if (ch >= '0' && ch <= '9' || ch == '-') { lexer.scanNumber(); if (lexer.token() == JSONToken.LITERAL_INT) { value = lexer.integerValue(); } else { value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal)); } map.put(key, value); } else if (ch == '[') { // 减少嵌套,兼容android lexer.nextToken(); JSONArray list = new JSONArray(); final boolean parentIsArray = fieldName != null && fieldName.getClass() == Integer.class; // if (!parentIsArray) { // this.setContext(context); // } if (fieldName == null) { this.setContext(context); } this.parseArray(list, key); if (lexer.isEnabled(Feature.UseObjectArray)) { value = list.toArray(); } else { value = list; } map.put(key, value); if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(); return object; } else if (lexer.token() == JSONToken.COMMA) { continue; } else { throw new JSONException(""syntax error""); } } else if (ch == '{') { // 减少嵌套,兼容 Android lexer.nextToken(); final boolean parentIsArray = fieldName != null && fieldName.getClass() == Integer.class; Map input; if (lexer.isEnabled(Feature.CustomMapDeserializer)) { MapDeserializer mapDeserializer = (MapDeserializer) config.getDeserializer(Map.class); input = (lexer.getFeatures() & Feature.OrderedField.mask) != 0 ? mapDeserializer.createMap(Map.class, lexer.getFeatures()) : mapDeserializer.createMap(Map.class); } else { input = new JSONObject(lexer.isEnabled(Feature.OrderedField)); } ParseContext ctxLocal = null; if (!parentIsArray) { ctxLocal = setContext(this.context, input, key); } Object obj = null; boolean objParsed = false; if (fieldTypeResolver != null) { String resolveFieldName = key != null ? key.toString() : null; Type fieldType = fieldTypeResolver.resolve(object, resolveFieldName); if (fieldType != null) { ObjectDeserializer fieldDeser = config.getDeserializer(fieldType); obj = fieldDeser.deserialze(this, fieldType, key); objParsed = true; } } if (!objParsed) { obj = this.parseObject(input, key); } if (ctxLocal != null && input != obj) { ctxLocal.object = object; } if (key != null) { checkMapResolve(object, key.toString()); } map.put(key, obj); if (parentIsArray) { //setContext(context, obj, key); setContext(obj, key); } if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(); setContext(context); return object; } else if (lexer.token() == JSONToken.COMMA) { if (parentIsArray) { this.popContext(); } else { this.setContext(context); } continue; } else { throw new JSONException(""syntax error, "" + lexer.tokenName()); } } else { lexer.nextToken(); value = parse(); map.put(key, value); if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(); return object; } else if (lexer.token() == JSONToken.COMMA) { continue; } else { throw new JSONException(""syntax error, position at "" + lexer.pos() + "", name "" + key); } } lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch == ',') { lexer.next(); continue; } else if (ch == '}') { lexer.next(); lexer.resetStringPosition(); lexer.nextToken(); // this.setContext(object, fieldName); this.setContext(value, key); return object; } else { throw new JSONException(""syntax error, position at "" + lexer.pos() + "", name "" + key); } } } finally { this.setContext(context); } } public ParserConfig getConfig() { return config; } public void setConfig(ParserConfig config) { this.config = config; } // compatible @SuppressWarnings(""unchecked"") public T parseObject(Class clazz) { return (T) parseObject(clazz, null); } public T parseObject(Type type) { return parseObject(type, null); } @SuppressWarnings(""unchecked"") public T parseObject(Type type, Object fieldName) { int token = lexer.token(); if (token == JSONToken.NULL) { lexer.nextToken(); return (T) TypeUtils.optionalEmpty(type); } if (token == JSONToken.LITERAL_STRING) { if (type == byte[].class) { byte[] bytes = lexer.bytesValue(); lexer.nextToken(); return (T) bytes; } if (type == char[].class) { String strVal = lexer.stringVal(); lexer.nextToken(); return (T) strVal.toCharArray(); } } ObjectDeserializer deserializer = config.getDeserializer(type); try { if (deserializer.getClass() == JavaBeanDeserializer.class) { if (lexer.token()!= JSONToken.LBRACE && lexer.token()!=JSONToken.LBRACKET) { throw new JSONException(""syntax error,expect start with { or [,but actually start with ""+ lexer.tokenName()); } return (T) ((JavaBeanDeserializer) deserializer).deserialze(this, type, fieldName, 0); } else { return (T) deserializer.deserialze(this, type, fieldName); } } catch (JSONException e) { throw e; } catch (Throwable e) { throw new JSONException(e.getMessage(), e); } } public List parseArray(Class clazz) { List array = new ArrayList(); parseArray(clazz, array); return array; } public void parseArray(Class clazz, @SuppressWarnings(""rawtypes"") Collection array) { parseArray((Type) clazz, array); } @SuppressWarnings(""rawtypes"") public void parseArray(Type type, Collection array) { parseArray(type, array, null); } @SuppressWarnings({ ""unchecked"", ""rawtypes"" }) public void parseArray(Type type, Collection array, Object fieldName) { int token = lexer.token(); if (token == JSONToken.SET || token == JSONToken.TREE_SET) { lexer.nextToken(); token = lexer.token(); } if (token != JSONToken.LBRACKET) { throw new JSONException(""field "" + fieldName + "" expect '[', but "" + JSONToken.name(token) + "", "" + lexer.info()); } ObjectDeserializer deserializer = null; if (int.class == type) { deserializer = IntegerCodec.instance; lexer.nextToken(JSONToken.LITERAL_INT); } else if (String.class == type) { deserializer = StringCodec.instance; lexer.nextToken(JSONToken.LITERAL_STRING); } else { deserializer = config.getDeserializer(type); lexer.nextToken(deserializer.getFastMatchToken()); } ParseContext context = this.context; this.setContext(array, fieldName); try { for (int i = 0;; ++i) { if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { while (lexer.token() == JSONToken.COMMA) { lexer.nextToken(); continue; } } if (lexer.token() == JSONToken.RBRACKET) { break; } if (int.class == type) { Object val = IntegerCodec.instance.deserialze(this, null, null); array.add(val); } else if (String.class == type) { String value; if (lexer.token() == JSONToken.LITERAL_STRING) { value = lexer.stringVal(); lexer.nextToken(JSONToken.COMMA); } else { Object obj = this.parse(); if (obj == null) { value = null; } else { value = obj.toString(); } } array.add(value); } else { Object val; if (lexer.token() == JSONToken.NULL) { lexer.nextToken(); val = null; } else { val = deserializer.deserialze(this, type, i); } array.add(val); checkListResolve(array); } if (lexer.token() == JSONToken.COMMA) { lexer.nextToken(deserializer.getFastMatchToken()); continue; } } } finally { this.setContext(context); } lexer.nextToken(JSONToken.COMMA); } public Object[] parseArray(Type[] types) { if (lexer.token() == JSONToken.NULL) { lexer.nextToken(JSONToken.COMMA); return null; } if (lexer.token() != JSONToken.LBRACKET) { throw new JSONException(""syntax error : "" + lexer.tokenName()); } Object[] list = new Object[types.length]; if (types.length == 0) { lexer.nextToken(JSONToken.RBRACKET); if (lexer.token() != JSONToken.RBRACKET) { throw new JSONException(""syntax error""); } lexer.nextToken(JSONToken.COMMA); return new Object[0]; } lexer.nextToken(JSONToken.LITERAL_INT); for (int i = 0; i < types.length; ++i) { Object value; if (lexer.token() == JSONToken.NULL) { value = null; lexer.nextToken(JSONToken.COMMA); } else { Type type = types[i]; if (type == int.class || type == Integer.class) { if (lexer.token() == JSONToken.LITERAL_INT) { value = Integer.valueOf(lexer.intValue()); lexer.nextToken(JSONToken.COMMA); } else { value = this.parse(); value = TypeUtils.cast(value, type, config); } } else if (type == String.class) { if (lexer.token() == JSONToken.LITERAL_STRING) { value = lexer.stringVal(); lexer.nextToken(JSONToken.COMMA); } else { value = this.parse(); value = TypeUtils.cast(value, type, config); } } else { boolean isArray = false; Class componentType = null; if (i == types.length - 1) { if (type instanceof Class) { Class clazz = (Class) type; //如果最后一个type是字节数组,且当前token为字符串类型,不应该当作可变长参数进行处理 //而是作为一个整体的Base64字符串进行反序列化 if (!((clazz == byte[].class || clazz == char[].class) && lexer.token() == LITERAL_STRING)) { isArray = clazz.isArray(); componentType = clazz.getComponentType(); } } } // support varArgs if (isArray && lexer.token() != JSONToken.LBRACKET) { List varList = new ArrayList(); ObjectDeserializer deserializer = config.getDeserializer(componentType); int fastMatch = deserializer.getFastMatchToken(); if (lexer.token() != JSONToken.RBRACKET) { for (;;) { Object item = deserializer.deserialze(this, type, null); varList.add(item); if (lexer.token() == JSONToken.COMMA) { lexer.nextToken(fastMatch); } else if (lexer.token() == JSONToken.RBRACKET) { break; } else { throw new JSONException(""syntax error :"" + JSONToken.name(lexer.token())); } } } value = TypeUtils.cast(varList, type, config); } else { ObjectDeserializer deserializer = config.getDeserializer(type); value = deserializer.deserialze(this, type, i); } } } list[i] = value; if (lexer.token() == JSONToken.RBRACKET) { break; } if (lexer.token() != JSONToken.COMMA) { throw new JSONException(""syntax error :"" + JSONToken.name(lexer.token())); } if (i == types.length - 1) { lexer.nextToken(JSONToken.RBRACKET); } else { lexer.nextToken(JSONToken.LITERAL_INT); } } if (lexer.token() != JSONToken.RBRACKET) { throw new JSONException(""syntax error""); } lexer.nextToken(JSONToken.COMMA); return list; } public void parseObject(Object object) { Class clazz = object.getClass(); JavaBeanDeserializer beanDeser = null; ObjectDeserializer deserializer = config.getDeserializer(clazz); if (deserializer instanceof JavaBeanDeserializer) { beanDeser = (JavaBeanDeserializer) deserializer; } if (lexer.token() != JSONToken.LBRACE && lexer.token() != JSONToken.COMMA) { throw new JSONException(""syntax error, expect {, actual "" + lexer.tokenName()); } for (;;) { // lexer.scanSymbol String key = lexer.scanSymbol(symbolTable); if (key == null) { if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(JSONToken.COMMA); break; } if (lexer.token() == JSONToken.COMMA) { if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { continue; } } } FieldDeserializer fieldDeser = null; if (beanDeser != null) { fieldDeser = beanDeser.getFieldDeserializer(key); } if (fieldDeser == null) { if (!lexer.isEnabled(Feature.IgnoreNotMatch)) { throw new JSONException(""setter not found, class "" + clazz.getName() + "", property "" + key); } lexer.nextTokenWithColon(); parse(); // skip if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(); return; } continue; } else { Class fieldClass = fieldDeser.fieldInfo.fieldClass; Type fieldType = fieldDeser.fieldInfo.fieldType; Object fieldValue; if (fieldClass == int.class) { lexer.nextTokenWithColon(JSONToken.LITERAL_INT); fieldValue = IntegerCodec.instance.deserialze(this, fieldType, null); } else if (fieldClass == String.class) { lexer.nextTokenWithColon(JSONToken.LITERAL_STRING); fieldValue = StringCodec.deserialze(this); } else if (fieldClass == long.class) { lexer.nextTokenWithColon(JSONToken.LITERAL_INT); fieldValue = LongCodec.instance.deserialze(this, fieldType, null); } else { ObjectDeserializer fieldValueDeserializer = config.getDeserializer(fieldClass, fieldType); lexer.nextTokenWithColon(fieldValueDeserializer.getFastMatchToken()); fieldValue = fieldValueDeserializer.deserialze(this, fieldType, null); } fieldDeser.setValue(object, fieldValue); } if (lexer.token() == JSONToken.COMMA) { continue; } if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(JSONToken.COMMA); return; } } } public Object parseArrayWithType(Type collectionType) { if (lexer.token() == JSONToken.NULL) { lexer.nextToken(); return null; } Type[] actualTypes = ((ParameterizedType) collectionType).getActualTypeArguments(); if (actualTypes.length != 1) { throw new JSONException(""not support type "" + collectionType); } Type actualTypeArgument = actualTypes[0]; if (actualTypeArgument instanceof Class) { List array = new ArrayList(); this.parseArray((Class) actualTypeArgument, array); return array; } if (actualTypeArgument instanceof WildcardType) { WildcardType wildcardType = (WildcardType) actualTypeArgument; // assert wildcardType.getUpperBounds().length == 1; Type upperBoundType = wildcardType.getUpperBounds()[0]; // assert upperBoundType instanceof Class; if (Object.class.equals(upperBoundType)) { if (wildcardType.getLowerBounds().length == 0) { // Collection return parse(); } else { throw new JSONException(""not support type : "" + collectionType); } } List array = new ArrayList(); this.parseArray((Class) upperBoundType, array); return array; // throw new JSONException(""not support type : "" + // collectionType);return parse(); } if (actualTypeArgument instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) actualTypeArgument; Type[] bounds = typeVariable.getBounds(); if (bounds.length != 1) { throw new JSONException(""not support : "" + typeVariable); } Type boundType = bounds[0]; if (boundType instanceof Class) { List array = new ArrayList(); this.parseArray((Class) boundType, array); return array; } } if (actualTypeArgument instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) actualTypeArgument; List array = new ArrayList(); this.parseArray(parameterizedType, array); return array; } throw new JSONException(""TODO : "" + collectionType); } public void acceptType(String typeName) { JSONLexer lexer = this.lexer; lexer.nextTokenWithColon(); if (lexer.token() != JSONToken.LITERAL_STRING) { throw new JSONException(""type not match error""); } if (typeName.equals(lexer.stringVal())) { lexer.nextToken(); if (lexer.token() == JSONToken.COMMA) { lexer.nextToken(); } } else { throw new JSONException(""type not match error""); } } public int getResolveStatus() { return resolveStatus; } public void setResolveStatus(int resolveStatus) { this.resolveStatus = resolveStatus; } public Object getObject(String path) { for (int i = 0; i < contextArrayIndex; ++i) { if (path.equals(contextArray[i].toString())) { return contextArray[i].object; } } return null; } @SuppressWarnings(""rawtypes"") public void checkListResolve(Collection array) { if (resolveStatus == NeedToResolve) { if (array instanceof List) { final int index = array.size() - 1; final List list = (List) array; ResolveTask task = getLastResolveTask(); task.fieldDeserializer = new ResolveFieldDeserializer(this, list, index); task.ownerContext = context; setResolveStatus(DefaultJSONParser.NONE); } else { ResolveTask task = getLastResolveTask(); task.fieldDeserializer = new ResolveFieldDeserializer(array); task.ownerContext = context; setResolveStatus(DefaultJSONParser.NONE); } } } @SuppressWarnings(""rawtypes"") public void checkMapResolve(Map object, Object fieldName) { if (resolveStatus == NeedToResolve) { ResolveFieldDeserializer fieldResolver = new ResolveFieldDeserializer(object, fieldName); ResolveTask task = getLastResolveTask(); task.fieldDeserializer = fieldResolver; task.ownerContext = context; setResolveStatus(DefaultJSONParser.NONE); } } @SuppressWarnings(""rawtypes"") public Object parseObject(final Map object) { return parseObject(object, null); } public JSONObject parseObject() { JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); Object parsedObject = parseObject(object); if (parsedObject instanceof JSONObject) { return (JSONObject) parsedObject; } if (parsedObject == null) { return null; } return new JSONObject((Map) parsedObject); } @SuppressWarnings(""rawtypes"") public final void parseArray(final Collection array) { parseArray(array, null); } @SuppressWarnings({ ""unchecked"", ""rawtypes"" }) public final void parseArray(final Collection array, Object fieldName) { final JSONLexer lexer = this.lexer; if (lexer.token() == JSONToken.SET || lexer.token() == JSONToken.TREE_SET) { lexer.nextToken(); } if (lexer.token() != JSONToken.LBRACKET) { throw new JSONException(""syntax error, expect [, actual "" + JSONToken.name(lexer.token()) + "", pos "" + lexer.pos() + "", fieldName "" + fieldName); } lexer.nextToken(JSONToken.LITERAL_STRING); if (this.context != null && this.context.level > 512) { throw new JSONException(""array level > 512""); } ParseContext context = this.context; this.setContext(array, fieldName); try { for (int i = 0; ; ++i) { if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { while (lexer.token() == JSONToken.COMMA) { lexer.nextToken(); continue; } } Object value; switch (lexer.token()) { case LITERAL_INT: value = lexer.integerValue(); lexer.nextToken(JSONToken.COMMA); break; case LITERAL_FLOAT: if (lexer.isEnabled(Feature.UseBigDecimal)) { value = lexer.decimalValue(true); } else { value = lexer.decimalValue(false); } lexer.nextToken(JSONToken.COMMA); break; case LITERAL_STRING: String stringLiteral = lexer.stringVal(); lexer.nextToken(JSONToken.COMMA); if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) { JSONScanner iso8601Lexer = new JSONScanner(stringLiteral); if (iso8601Lexer.scanISO8601DateIfMatch()) { value = iso8601Lexer.getCalendar().getTime(); } else { value = stringLiteral; } iso8601Lexer.close(); } else { value = stringLiteral; } break; case TRUE: value = Boolean.TRUE; lexer.nextToken(JSONToken.COMMA); break; case FALSE: value = Boolean.FALSE; lexer.nextToken(JSONToken.COMMA); break; case LBRACE: JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); value = parseObject(object, i); break; case LBRACKET: Collection items = new JSONArray(); parseArray(items, i); if (lexer.isEnabled(Feature.UseObjectArray)) { value = items.toArray(); } else { value = items; } break; case NULL: value = null; lexer.nextToken(JSONToken.LITERAL_STRING); break; case UNDEFINED: value = null; lexer.nextToken(JSONToken.LITERAL_STRING); break; case RBRACKET: lexer.nextToken(JSONToken.COMMA); return; case EOF: throw new JSONException(""unclosed jsonArray""); default: value = parse(); break; } array.add(value); checkListResolve(array); if (lexer.token() == JSONToken.COMMA) { lexer.nextToken(JSONToken.LITERAL_STRING); continue; } } } catch (ClassCastException e) { throw new JSONException(""unkown error"", e); } finally { this.setContext(context); } } public ParseContext getContext() { return context; } public ParseContext getOwnerContext() { return context.parent; } public List getResolveTaskList() { if (resolveTaskList == null) { resolveTaskList = new ArrayList(2); } return resolveTaskList; } public void addResolveTask(ResolveTask task) { if (resolveTaskList == null) { resolveTaskList = new ArrayList(2); } resolveTaskList.add(task); } public ResolveTask getLastResolveTask() { return resolveTaskList.get(resolveTaskList.size() - 1); } public List getExtraProcessors() { if (extraProcessors == null) { extraProcessors = new ArrayList(2); } return extraProcessors; } public List getExtraTypeProviders() { if (extraTypeProviders == null) { extraTypeProviders = new ArrayList(2); } return extraTypeProviders; } public FieldTypeResolver getFieldTypeResolver() { return fieldTypeResolver; } public void setFieldTypeResolver(FieldTypeResolver fieldTypeResolver) { this.fieldTypeResolver = fieldTypeResolver; } public void setContext(ParseContext context) { if (lexer.isEnabled(Feature.DisableCircularReferenceDetect)) { return; } this.context = context; } public void popContext() { if (lexer.isEnabled(Feature.DisableCircularReferenceDetect)) { return; } this.context = this.context.parent; if (contextArrayIndex <= 0) { return; } contextArrayIndex--; contextArray[contextArrayIndex] = null; } public ParseContext setContext(Object object, Object fieldName) { if (lexer.isEnabled(Feature.DisableCircularReferenceDetect)) { return null; } return setContext(this.context, object, fieldName); } public ParseContext setContext(ParseContext parent, Object object, Object fieldName) { if (lexer.isEnabled(Feature.DisableCircularReferenceDetect)) { return null; } this.context = new ParseContext(parent, object, fieldName); addContext(this.context); return this.context; } private void addContext(ParseContext context) { int i = contextArrayIndex++; if (contextArray == null) { contextArray = new ParseContext[8]; } else if (i >= contextArray.length) { int newLen = (contextArray.length * 3) / 2; ParseContext[] newArray = new ParseContext[newLen]; System.arraycopy(contextArray, 0, newArray, 0, contextArray.length); contextArray = newArray; } contextArray[i] = context; } public Object parse() { return parse(null); } public Object parseKey() { if (lexer.token() == JSONToken.IDENTIFIER) { String value = lexer.stringVal(); lexer.nextToken(JSONToken.COMMA); return value; } return parse(null); } public Object parse(Object fieldName) { final JSONLexer lexer = this.lexer; switch (lexer.token()) { case SET: lexer.nextToken(); HashSet set = new HashSet(); parseArray(set, fieldName); return set; case TREE_SET: lexer.nextToken(); TreeSet treeSet = new TreeSet(); parseArray(treeSet, fieldName); return treeSet; case LBRACKET: Collection array = isEnabled(Feature.UseNativeJavaObject) ? new ArrayList() : new JSONArray(); parseArray(array, fieldName); if (lexer.isEnabled(Feature.UseObjectArray)) { return array.toArray(); } return array; case LBRACE: Map object = isEnabled(Feature.UseNativeJavaObject) ? lexer.isEnabled(Feature.OrderedField) ? new HashMap() : new LinkedHashMap() : new JSONObject(lexer.isEnabled(Feature.OrderedField)); return parseObject(object, fieldName); // case LBRACE: { // Map map = lexer.isEnabled(Feature.OrderedField) // ? new LinkedHashMap() // : new HashMap(); // Object obj = parseObject(map, fieldName); // if (obj != map) { // return obj; // } // return new JSONObject(map); // } case LITERAL_INT: Number intValue = lexer.integerValue(); lexer.nextToken(); return intValue; case LITERAL_FLOAT: Object value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal)); lexer.nextToken(); return value; case LITERAL_STRING: String stringLiteral = lexer.stringVal(); lexer.nextToken(JSONToken.COMMA); if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) { JSONScanner iso8601Lexer = new JSONScanner(stringLiteral); try { if (iso8601Lexer.scanISO8601DateIfMatch()) { return iso8601Lexer.getCalendar().getTime(); } } finally { iso8601Lexer.close(); } } return stringLiteral; case NULL: lexer.nextToken(); return null; case UNDEFINED: lexer.nextToken(); return null; case TRUE: lexer.nextToken(); return Boolean.TRUE; case FALSE: lexer.nextToken(); return Boolean.FALSE; case NEW: lexer.nextToken(JSONToken.IDENTIFIER); if (lexer.token() != JSONToken.IDENTIFIER) { throw new JSONException(""syntax error""); } lexer.nextToken(JSONToken.LPAREN); accept(JSONToken.LPAREN); long time = lexer.integerValue().longValue(); accept(JSONToken.LITERAL_INT); accept(JSONToken.RPAREN); return new Date(time); case EOF: if (lexer.isBlankInput()) { return null; } throw new JSONException(""unterminated json string, "" + lexer.info()); case HEX: byte[] bytes = lexer.bytesValue(); lexer.nextToken(); return bytes; case IDENTIFIER: String identifier = lexer.stringVal(); if (""NaN"".equals(identifier)) { lexer.nextToken(); return null; } throw new JSONException(""syntax error, "" + lexer.info()); case ERROR: default: throw new JSONException(""syntax error, "" + lexer.info()); } } public void config(Feature feature, boolean state) { this.lexer.config(feature, state); } public boolean isEnabled(Feature feature) { return lexer.isEnabled(feature); } public JSONLexer getLexer() { return lexer; } public final void accept(final int token) { final JSONLexer lexer = this.lexer; if (lexer.token() == token) { lexer.nextToken(); } else { throw new JSONException(""syntax error, expect "" + JSONToken.name(token) + "", actual "" + JSONToken.name(lexer.token())); } } public final void accept(final int token, int nextExpectToken) { final JSONLexer lexer = this.lexer; if (lexer.token() == token) { lexer.nextToken(nextExpectToken); } else { throwException(token); } } public void throwException(int token) { throw new JSONException(""syntax error, expect "" + JSONToken.name(token) + "", actual "" + JSONToken.name(lexer.token())); } public void close() { final JSONLexer lexer = this.lexer; try { if (lexer.isEnabled(Feature.AutoCloseSource)) { if (lexer.token() != JSONToken.EOF) { throw new JSONException(""not close json text, token : "" + JSONToken.name(lexer.token())); } } } finally { lexer.close(); } } public Object resolveReference(String ref) { if(contextArray == null) { return null; } for (int i = 0; i < contextArray.length && i < contextArrayIndex; i++) { ParseContext context = contextArray[i]; if (context.toString().equals(ref)) { return context.object; } } return null; } public void handleResovleTask(Object value) { if (resolveTaskList == null) { return; } for (int i = 0, size = resolveTaskList.size(); i < size; ++i) { ResolveTask task = resolveTaskList.get(i); String ref = task.referenceValue; Object object = null; if (task.ownerContext != null) { object = task.ownerContext.object; } Object refValue; if (ref.startsWith(""$"")) { refValue = getObject(ref); if (refValue == null) { try { JSONPath jsonpath = new JSONPath(ref, SerializeConfig.getGlobalInstance(), config, true); if (jsonpath.isRef()) { refValue = jsonpath.eval(value); } } catch (JSONPathException ex) { // skip } } } else { refValue = task.context.object; } FieldDeserializer fieldDeser = task.fieldDeserializer; if (fieldDeser != null) { if (refValue != null && refValue.getClass() == JSONObject.class && fieldDeser.fieldInfo != null && !Map.class.isAssignableFrom(fieldDeser.fieldInfo.fieldClass)) { Object root = this.contextArray[0].object; JSONPath jsonpath = JSONPath.compile(ref); if (jsonpath.isRef()) { refValue = jsonpath.eval(root); } } // workaround for bug if (fieldDeser.getOwnerClass() != null && (!fieldDeser.getOwnerClass().isInstance(object)) && task.ownerContext.parent != null ) { for (ParseContext ctx = task.ownerContext.parent;ctx != null;ctx = ctx.parent) { if (fieldDeser.getOwnerClass().isInstance(ctx.object)) { object = ctx.object; break; } } } fieldDeser.setValue(object, refValue); } } } public static class ResolveTask { public final ParseContext context; public final String referenceValue; public FieldDeserializer fieldDeserializer; public ParseContext ownerContext; public ResolveTask(ParseContext context, String referenceValue){ this.context = context; this.referenceValue = referenceValue; } } public void parseExtra(Object object, String key) { final JSONLexer lexer = this.lexer; // xxx lexer.nextTokenWithColon(); Type type = null; if (extraTypeProviders != null) { for (ExtraTypeProvider extraProvider : extraTypeProviders) { type = extraProvider.getExtraType(object, key); } } Object value = type == null // ? parse() // skip : parseObject(type); if (object instanceof ExtraProcessable) { ExtraProcessable extraProcessable = ((ExtraProcessable) object); extraProcessable.processExtra(key, value); return; } if (extraProcessors != null) { for (ExtraProcessor process : extraProcessors) { process.processExtra(object, key, value); } } if (resolveStatus == NeedToResolve) { resolveStatus = NONE; } } public Object parse(PropertyProcessable object, Object fieldName) { if (lexer.token() != JSONToken.LBRACE) { String msg = ""syntax error, expect {, actual "" + lexer.tokenName(); if (fieldName instanceof String) { msg += "", fieldName ""; msg += fieldName; } msg += "", ""; msg += lexer.info(); JSONArray array = new JSONArray(); parseArray(array, fieldName); if (array.size() == 1) { Object first = array.get(0); if (first instanceof JSONObject) { return (JSONObject) first; } } throw new JSONException(msg); } ParseContext context = this.context; try { for (int i = 0;;++i) { lexer.skipWhitespace(); char ch = lexer.getCurrent(); if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { while (ch == ',') { lexer.next(); lexer.skipWhitespace(); ch = lexer.getCurrent(); } } String key; if (ch == '""') { key = lexer.scanSymbol(symbolTable, '""'); lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException(""expect ':' at "" + lexer.pos()); } } else if (ch == '}') { lexer.next(); lexer.resetStringPosition(); lexer.nextToken(JSONToken.COMMA); return object; } else if (ch == '\'') { if (!lexer.isEnabled(Feature.AllowSingleQuotes)) { throw new JSONException(""syntax error""); } key = lexer.scanSymbol(symbolTable, '\''); lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException(""expect ':' at "" + lexer.pos()); } } else { if (!lexer.isEnabled(Feature.AllowUnQuotedFieldNames)) { throw new JSONException(""syntax error""); } key = lexer.scanSymbolUnQuoted(symbolTable); lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException(""expect ':' at "" + lexer.pos() + "", actual "" + ch); } } lexer.next(); lexer.skipWhitespace(); ch = lexer.getCurrent(); lexer.resetStringPosition(); if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) { String typeName = lexer.scanSymbol(symbolTable, '""'); Class clazz = config.checkAutoType(typeName, null, lexer.getFeatures()); if (Map.class.isAssignableFrom(clazz) ) { lexer.nextToken(JSONToken.COMMA); if (lexer.token() == JSONToken.RBRACE) { lexer.nextToken(JSONToken.COMMA); return object; } continue; } ObjectDeserializer deserializer = config.getDeserializer(clazz); lexer.nextToken(JSONToken.COMMA); setResolveStatus(DefaultJSONParser.TypeNameRedirect); if (context != null && !(fieldName instanceof Integer)) { popContext(); } return (Map) deserializer.deserialze(this, clazz, fieldName); } Object value; lexer.nextToken(); if (i != 0) { setContext(context); } Type [MASK] = object.getType(key); if (lexer.token() == JSONToken.NULL) { value = null; lexer.nextToken(); } else { value = parseObject( [MASK] , key); } object.apply(key, value); setContext(context, value, key); setContext(context); final int tok = lexer.token(); if (tok == JSONToken.EOF || tok == JSONToken.RBRACKET) { return object; } if (tok == JSONToken.RBRACE) { lexer.nextToken(); return object; } } } finally { setContext(context); } } } ","valueType " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.tests.bullet; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Mesh; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; import com.badlogic.gdx.physics.bullet.collision.btConvexHullShape; import com.badlogic.gdx.physics.bullet.collision.btShapeHull; /** @author xoppa */ public class ConvexHullTest extends BaseBulletTest { @Override public void create () { super.create(); final Model carModel = objLoader.loadModel(Gdx.files.internal(""data/car.obj"")); disposables.add(carModel); carModel.materials.get(0).clear(); carModel.materials.get(0).set(ColorAttribute.createDiffuse(Color.WHITE), ColorAttribute.createSpecular(Color.WHITE)); world.addConstructor(""car"", new BulletConstructor(carModel, 5f, createConvexHullShape(carModel, true))); // Create the entities world.add(""ground"", 0f, 0f, 0f).setColor(0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 1f); for (float y = 10f; y < 50f; y += 5f) world.add(""car"", -2f + (float)Math.random() * 4f, y, -2f + (float)Math.random() * 4f).setColor( 0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 1f); } @Override public boolean tap (float x, float y, int count, int button) { shoot(x, y); return true; } public static btConvexHullShape createConvexHullShape (final Model model, boolean [MASK] ) { final Mesh mesh = model.meshes.get(0); final btConvexHullShape shape = new btConvexHullShape(mesh.getVerticesBuffer(false), mesh.getNumVertices(), mesh.getVertexSize()); if (! [MASK] ) return shape; // now [MASK] the shape final btShapeHull hull = new btShapeHull(shape); hull.buildHull(shape.getMargin()); final btConvexHullShape result = new btConvexHullShape(hull); // delete the temporary shape shape.dispose(); hull.dispose(); return result; } } ","optimize " "package com.blankj.base_transform.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; /** *
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/08/27
 *     desc  : utils about zip or jar
 * 
*/ public final class ZipUtils { private static final int BUFFER_LEN = 8192; private ZipUtils() { throw new UnsupportedOperationException(""u can't instantiate me...""); } /** * Zip the files. * * @param srcFiles The source of files. * @param zipFilePath The path of ZIP file. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFiles(final Collection srcFiles, final String zipFilePath) throws IOException { return zipFiles(srcFiles, zipFilePath, null); } /** * Zip the files. * * @param srcFilePaths The paths of source files. * @param zipFilePath The path of ZIP file. * @param comment The comment. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFiles(final Collection srcFilePaths, final String zipFilePath, final String comment) throws IOException { if (srcFilePaths == null || zipFilePath == null) return false; ZipOutputStream zos = null; try { zos = new ZipOutputStream(new FileOutputStream(zipFilePath)); for (String srcFile : srcFilePaths) { if (!zipFile(getFileByPath(srcFile), """", zos, comment)) return false; } return true; } finally { if (zos != null) { zos.finish(); zos.close(); } } } /** * Zip the files. * * @param srcFiles The source of files. * @param zipFile The ZIP file. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFiles(final Collection srcFiles, final File zipFile) throws IOException { return zipFiles(srcFiles, zipFile, null); } /** * Zip the files. * * @param srcFiles The source of files. * @param zipFile The ZIP file. * @param comment The comment. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFiles(final Collection srcFiles, final File zipFile, final String comment) throws IOException { if (srcFiles == null || zipFile == null) return false; ZipOutputStream zos = null; try { zos = new ZipOutputStream(new FileOutputStream(zipFile)); for (File srcFile : srcFiles) { if (!zipFile(srcFile, """", zos, comment)) return false; } return true; } finally { if (zos != null) { zos.finish(); zos.close(); } } } /** * Zip the file. * * @param srcFilePath The path of source file. * @param zipFilePath The path of ZIP file. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFile(final String srcFilePath, final String zipFilePath) throws IOException { return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), null); } /** * Zip the file. * * @param srcFilePath The path of source file. * @param zipFilePath The path of ZIP file. * @param comment The comment. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFile(final String srcFilePath, final String zipFilePath, final String comment) throws IOException { return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), comment); } /** * Zip the file. * * @param srcFile The source of file. * @param zipFile The ZIP file. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFile(final File srcFile, final File zipFile) throws IOException { return zipFile(srcFile, zipFile, null); } /** * Zip the file. * * @param srcFile The source of file. * @param zipFile The ZIP file. * @param comment The comment. * @return {@code true}: success
{@code false}: fail * @throws IOException if an I/O error has occurred */ public static boolean zipFile(final File srcFile, final File zipFile, final String comment) throws IOException { if (srcFile == null || zipFile == null) return false; ZipOutputStream zos = null; try { zos = new ZipOutputStream(new FileOutputStream(zipFile)); return zipFile(srcFile, """", zos, comment); } finally { if (zos != null) { zos.close(); } } } private static boolean zipFile(final File srcFile, String rootPath, final ZipOutputStream zos, final String comment) throws IOException { rootPath = rootPath + (isSpace(rootPath) ? """" : File.separator) + srcFile.getName(); if (srcFile.isDirectory()) { File[] fileList = srcFile.listFiles(); if (fileList == null || fileList.length <= 0) { ZipEntry entry = new ZipEntry(rootPath + '/'); entry.setComment(comment); zos.putNextEntry(entry); zos.closeEntry(); } else { for (File file : fileList) { if (!zipFile(file, rootPath, zos, comment)) return false; } } } else { InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(srcFile)); ZipEntry entry = new ZipEntry(rootPath); entry.setComment(comment); zos.putNextEntry(entry); byte buffer[] = new byte[BUFFER_LEN]; int len; while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) { zos.write(buffer, 0, len); } zos.closeEntry(); } finally { if (is != null) { is.close(); } } } return true; } /** * Unzip the file. * * @param zipFilePath The path of ZIP file. * @param destDirPath The path of destination directory. * @return the unzipped files * @throws IOException if unzip unsuccessfully */ public static List unzipFile(final String zipFilePath, final String destDirPath) throws IOException { return unzipFileByKeyword(zipFilePath, destDirPath, null); } /** * Unzip the file. * * @param zipFile The ZIP file. * @param destDir The destination directory. * @return the unzipped files * @throws IOException if unzip unsuccessfully */ public static List unzipFile(final File zipFile, final File destDir) throws IOException { return unzipFileByKeyword(zipFile, destDir, null); } /** * Unzip the file by keyword. * * @param zipFilePath The path of ZIP file. * @param destDirPath The path of destination directory. * @param keyword The keyboard. * @return the unzipped files * @throws IOException if unzip unsuccessfully */ public static List unzipFileByKeyword(final String zipFilePath, final String destDirPath, final String keyword) throws IOException { return unzipFileByKeyword(getFileByPath(zipFilePath), getFileByPath(destDirPath), keyword); } /** * Unzip the file by keyword. * * @param zipFile The ZIP file. * @param destDir The destination directory. * @param keyword The keyboard. * @return the unzipped files * @throws IOException if unzip unsuccessfully */ public static List unzipFileByKeyword(final File zipFile, final File destDir, final String keyword) throws IOException { if (zipFile == null || destDir == null) return null; List files = new ArrayList<>(); ZipFile zip = new ZipFile(zipFile); Enumeration entries = zip.entries(); try { if (isSpace(keyword)) { while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); String [MASK] = entry.getName(); if ( [MASK] .contains(""../"")) { System.err.println("" [MASK] : "" + [MASK] + "" is dangerous!""); continue; } if (!unzipChildFile(destDir, files, zip, entry, [MASK] )) return files; } } else { while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); String [MASK] = entry.getName(); if ( [MASK] .contains(""../"")) { System.out.println("" [MASK] : "" + [MASK] + "" is dangerous!""); continue; } if ( [MASK] .contains(keyword)) { if (!unzipChildFile(destDir, files, zip, entry, [MASK] )) return files; } } } } finally { zip.close(); } return files; } private static boolean unzipChildFile(final File destDir, final List files, final ZipFile zip, final ZipEntry entry, final String name) throws IOException { File file = new File(destDir, name); files.add(file); if (entry.isDirectory()) { return createOrExistsDir(file); } else { if (!createOrExistsFile(file)) return false; InputStream in = null; OutputStream out = null; try { in = new BufferedInputStream(zip.getInputStream(entry)); out = new BufferedOutputStream(new FileOutputStream(file)); byte buffer[] = new byte[BUFFER_LEN]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } return true; } /** * Return the files' path in ZIP file. * * @param zipFilePath The path of ZIP file. * @return the files' path in ZIP file * @throws IOException if an I/O error has occurred */ public static List getFilesPath(final String zipFilePath) throws IOException { return getFilesPath(getFileByPath(zipFilePath)); } /** * Return the files' path in ZIP file. * * @param zipFile The ZIP file. * @return the files' path in ZIP file * @throws IOException if an I/O error has occurred */ public static List getFilesPath(final File zipFile) throws IOException { if (zipFile == null) return null; List paths = new ArrayList<>(); ZipFile zip = new ZipFile(zipFile); Enumeration entries = zip.entries(); while (entries.hasMoreElements()) { String [MASK] = ((ZipEntry) entries.nextElement()).getName(); if ( [MASK] .contains(""../"")) { System.out.println("" [MASK] : "" + [MASK] + "" is dangerous!""); paths.add( [MASK] ); } else { paths.add( [MASK] ); } } zip.close(); return paths; } /** * Return the files' comment in ZIP file. * * @param zipFilePath The path of ZIP file. * @return the files' comment in ZIP file * @throws IOException if an I/O error has occurred */ public static List getComments(final String zipFilePath) throws IOException { return getComments(getFileByPath(zipFilePath)); } /** * Return the files' comment in ZIP file. * * @param zipFile The ZIP file. * @return the files' comment in ZIP file * @throws IOException if an I/O error has occurred */ public static List getComments(final File zipFile) throws IOException { if (zipFile == null) return null; List comments = new ArrayList<>(); ZipFile zip = new ZipFile(zipFile); Enumeration entries = zip.entries(); while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); comments.add(entry.getComment()); } zip.close(); return comments; } private static boolean createOrExistsDir(final File file) { return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); } private static boolean createOrExistsFile(final File file) { if (file == null) return false; if (file.exists()) return file.isFile(); if (!createOrExistsDir(file.getParentFile())) return false; try { return file.createNewFile(); } catch (IOException e) { e.printStackTrace(); return false; } } private static File getFileByPath(final String filePath) { return isSpace(filePath) ? null : new File(filePath); } private static boolean isSpace(final String s) { if (s == null) return true; for (int i = 0, len = s.length(); i < len; ++i) { if (!Character.isWhitespace(s.charAt(i))) { return false; } } return true; } } ","entryName " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.nio; /** CharArrayBuffer, ReadWriteCharArrayBuffer and ReadOnlyCharArrayBuffer compose the implementation of array based char buffers. *

* ReadWriteCharArrayBuffer extends CharArrayBuffer with all the write methods. *

*

* This class is marked final for runtime performance. *

*/ final class ReadWriteCharArrayBuffer extends CharArrayBuffer { static ReadWriteCharArrayBuffer copy (CharArrayBuffer [MASK] , int markOfOther) { ReadWriteCharArrayBuffer buf = new ReadWriteCharArrayBuffer( [MASK] .capacity(), [MASK] .backingArray, [MASK] .offset); buf.limit = [MASK] .limit(); buf.position = [MASK] .position(); buf.mark = markOfOther; return buf; } ReadWriteCharArrayBuffer (char[] array) { super(array); } ReadWriteCharArrayBuffer (int capacity) { super(capacity); } ReadWriteCharArrayBuffer (int capacity, char[] backingArray, int arrayOffset) { super(capacity, backingArray, arrayOffset); } public CharBuffer asReadOnlyBuffer () { return ReadOnlyCharArrayBuffer.copy(this, mark); } public CharBuffer compact () { System.arraycopy(backingArray, position + offset, backingArray, offset, remaining()); position = limit - position; limit = capacity; mark = UNSET_MARK; return this; } public CharBuffer duplicate () { return copy(this, mark); } public boolean isReadOnly () { return false; } protected char[] protectedArray () { return backingArray; } protected int protectedArrayOffset () { return offset; } protected boolean protectedHasArray () { return true; } public CharBuffer put (char c) { if (position == limit) { throw new BufferOverflowException(); } backingArray[offset + position++] = c; return this; } public CharBuffer put (int index, char c) { if (index < 0 || index >= limit) { throw new IndexOutOfBoundsException(); } backingArray[offset + index] = c; return this; } public CharBuffer put (char[] src, int off, int len) { int length = src.length; if (off < 0 || len < 0 || (long)len + (long)off > length) { throw new IndexOutOfBoundsException(); } if (len > remaining()) { throw new BufferOverflowException(); } System.arraycopy(src, off, backingArray, offset + position, len); position += len; return this; } public CharBuffer slice () { return new ReadWriteCharArrayBuffer(remaining(), backingArray, offset + position); } } ","other " "package com.github.mikephil.charting.jobs; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.view.View; import com.github.mikephil.charting.utils.ObjectPool; import com.github.mikephil.charting.utils.Transformer; import com.github.mikephil.charting.utils.ViewPortHandler; /** * Created by Philipp Jahoda on 19/02/16. */ @SuppressLint(""NewApi"") public class AnimatedMoveViewJob extends AnimatedViewPortJob { private static ObjectPool pool; static { pool = ObjectPool.create(4, new AnimatedMoveViewJob(null,0,0,null,null,0,0,0)); pool.setReplenishPercentage(0.5f); } public static AnimatedMoveViewJob getInstance(ViewPortHandler viewPortHandler, float xValue, float yValue, Transformer trans, View v, float xOrigin, float yOrigin, long [MASK] ){ AnimatedMoveViewJob result = pool.get(); result.mViewPortHandler = viewPortHandler; result.xValue = xValue; result.yValue = yValue; result.mTrans = trans; result.view = v; result.xOrigin = xOrigin; result.yOrigin = yOrigin; //result.resetAnimator(); result.animator.setDuration( [MASK] ); return result; } public static void recycleInstance(AnimatedMoveViewJob instance){ pool.recycle(instance); } public AnimatedMoveViewJob(ViewPortHandler viewPortHandler, float xValue, float yValue, Transformer trans, View v, float xOrigin, float yOrigin, long [MASK] ) { super(viewPortHandler, xValue, yValue, trans, v, xOrigin, yOrigin, [MASK] ); } @Override public void onAnimationUpdate(ValueAnimator animation) { pts[0] = xOrigin + (xValue - xOrigin) * phase; pts[1] = yOrigin + (yValue - yOrigin) * phase; mTrans.pointValuesToPixel(pts); mViewPortHandler.centerViewPort(pts, view); } public void recycleSelf(){ recycleInstance(this); } @Override protected ObjectPool.Poolable instantiate() { return new AnimatedMoveViewJob(null,0,0,null,null,0,0,0); } } ","duration " "/* * Copyright 2015 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.ssl; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import io.netty.handler.ssl.util.SimpleTrustManagerFactory; import io.netty.util.CharsetUtil; import io.netty.util.NetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.PromiseNotifier; import io.netty.util.internal.ResourcesUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.SystemPropertyUtil; import org.conscrypt.OpenSSLProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.opentest4j.AssertionFailedError; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.file.Files; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; import java.security.Provider; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.crypto.SecretKey; import javax.net.ssl.ExtendedSSLSession; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactorySpi; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.Status; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactorySpi; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; import javax.security.cert.X509Certificate; import static io.netty.handler.ssl.SslUtils.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.mockito.Mockito.verify; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class SSLEngineTest { private static final String PRINCIPAL_NAME = ""CN=e8ac02fa0d65a84219016045db8b05c485b4ecdf.netty.test""; private final boolean tlsv13Supported; @Mock protected MessageReceiver serverReceiver; @Mock protected MessageReceiver clientReceiver; protected Throwable serverException; protected Throwable clientException; protected SslContext serverSslCtx; protected SslContext clientSslCtx; protected ServerBootstrap sb; protected Bootstrap cb; protected Channel serverChannel; protected Channel serverConnectedChannel; protected Channel clientChannel; protected CountDownLatch serverLatch; protected CountDownLatch clientLatch; interface MessageReceiver { void messageReceived(ByteBuf msg); } protected static final class MessageDelegatorChannelHandler extends SimpleChannelInboundHandler { private final MessageReceiver receiver; private final CountDownLatch latch; public MessageDelegatorChannelHandler(MessageReceiver receiver, CountDownLatch latch) { super(false); this.receiver = receiver; this.latch = latch; } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { receiver.messageReceived(msg); latch.countDown(); } } enum BufferType { Direct, Heap, Mixed } static final class ProtocolCipherCombo { private static final ProtocolCipherCombo TLSV12 = new ProtocolCipherCombo( SslProtocols.TLS_v1_2, ""TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256""); private static final ProtocolCipherCombo TLSV13 = new ProtocolCipherCombo( SslProtocols.TLS_v1_3, ""TLS_AES_128_GCM_SHA256""); final String protocol; final String cipher; private ProtocolCipherCombo(String protocol, String cipher) { this.protocol = protocol; this.cipher = cipher; } static ProtocolCipherCombo tlsv12() { return TLSV12; } static ProtocolCipherCombo tlsv13() { return TLSV13; } @Override public String toString() { return ""ProtocolCipherCombo{"" + ""protocol='"" + protocol + '\'' + "", cipher='"" + cipher + '\'' + '}'; } } protected SSLEngineTest(boolean tlsv13Supported) { this.tlsv13Supported = tlsv13Supported; } protected static class SSLEngineTestParam { private final BufferType type; private final ProtocolCipherCombo protocolCipherCombo; private final boolean delegate; SSLEngineTestParam(BufferType type, ProtocolCipherCombo protocolCipherCombo, boolean delegate) { this.type = type; this.protocolCipherCombo = protocolCipherCombo; this.delegate = delegate; } final BufferType type() { return type; } final ProtocolCipherCombo combo() { return protocolCipherCombo; } final boolean delegate() { return delegate; } final List protocols() { return Collections.singletonList(protocolCipherCombo.protocol); } final List ciphers() { return Collections.singletonList(protocolCipherCombo.cipher); } } protected List newTestParams() { List params = new ArrayList(); for (BufferType type: BufferType.values()) { params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv12(), false)); params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv12(), true)); if (tlsv13Supported) { params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv13(), false)); params.add(new SSLEngineTestParam(type, ProtocolCipherCombo.tlsv13(), true)); } } return params; } private ExecutorService delegatingExecutor; protected ByteBuffer allocateBuffer(BufferType type, int len) { switch (type) { case Direct: return ByteBuffer.allocateDirect(len); case Heap: return ByteBuffer.allocate(len); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? ByteBuffer.allocateDirect(len) : ByteBuffer.allocate(len); default: throw new Error(); } } private static final class TestByteBufAllocator implements ByteBufAllocator { private final ByteBufAllocator allocator; private final BufferType type; TestByteBufAllocator(ByteBufAllocator allocator, BufferType type) { this.allocator = allocator; this.type = type; } @Override public ByteBuf buffer() { switch (type) { case Direct: return allocator.directBuffer(); case Heap: return allocator.heapBuffer(); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.directBuffer() : allocator.heapBuffer(); default: throw new Error(); } } @Override public ByteBuf buffer(int initialCapacity) { switch (type) { case Direct: return allocator.directBuffer(initialCapacity); case Heap: return allocator.heapBuffer(initialCapacity); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.directBuffer(initialCapacity) : allocator.heapBuffer(initialCapacity); default: throw new Error(); } } @Override public ByteBuf buffer(int initialCapacity, int maxCapacity) { switch (type) { case Direct: return allocator.directBuffer(initialCapacity, maxCapacity); case Heap: return allocator.heapBuffer(initialCapacity, maxCapacity); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.directBuffer(initialCapacity, maxCapacity) : allocator.heapBuffer(initialCapacity, maxCapacity); default: throw new Error(); } } @Override public ByteBuf ioBuffer() { return allocator.ioBuffer(); } @Override public ByteBuf ioBuffer(int initialCapacity) { return allocator.ioBuffer(initialCapacity); } @Override public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) { return allocator.ioBuffer(initialCapacity, maxCapacity); } @Override public ByteBuf heapBuffer() { return allocator.heapBuffer(); } @Override public ByteBuf heapBuffer(int initialCapacity) { return allocator.heapBuffer(initialCapacity); } @Override public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) { return allocator.heapBuffer(initialCapacity, maxCapacity); } @Override public ByteBuf directBuffer() { return allocator.directBuffer(); } @Override public ByteBuf directBuffer(int initialCapacity) { return allocator.directBuffer(initialCapacity); } @Override public ByteBuf directBuffer(int initialCapacity, int maxCapacity) { return allocator.directBuffer(initialCapacity, maxCapacity); } @Override public CompositeByteBuf compositeBuffer() { switch (type) { case Direct: return allocator.compositeDirectBuffer(); case Heap: return allocator.compositeHeapBuffer(); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.compositeDirectBuffer() : allocator.compositeHeapBuffer(); default: throw new Error(); } } @Override public CompositeByteBuf compositeBuffer(int maxNumComponents) { switch (type) { case Direct: return allocator.compositeDirectBuffer(maxNumComponents); case Heap: return allocator.compositeHeapBuffer(maxNumComponents); case Mixed: return PlatformDependent.threadLocalRandom().nextBoolean() ? allocator.compositeDirectBuffer(maxNumComponents) : allocator.compositeHeapBuffer(maxNumComponents); default: throw new Error(); } } @Override public CompositeByteBuf compositeHeapBuffer() { return allocator.compositeHeapBuffer(); } @Override public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) { return allocator.compositeHeapBuffer(maxNumComponents); } @Override public CompositeByteBuf compositeDirectBuffer() { return allocator.compositeDirectBuffer(); } @Override public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) { return allocator.compositeDirectBuffer(maxNumComponents); } @Override public boolean isDirectBufferPooled() { return allocator.isDirectBufferPooled(); } @Override public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { return allocator.calculateNewCapacity(minNewCapacity, maxCapacity); } } @BeforeEach public void setup() { MockitoAnnotations.initMocks(this); serverLatch = new CountDownLatch(1); clientLatch = new CountDownLatch(1); delegatingExecutor = Executors.newCachedThreadPool(); } @AfterEach public void tearDown() throws InterruptedException { ChannelFuture clientCloseFuture = null; ChannelFuture serverConnectedCloseFuture = null; ChannelFuture serverCloseFuture = null; if (clientChannel != null) { clientCloseFuture = clientChannel.close(); clientChannel = null; } if (serverConnectedChannel != null) { serverConnectedCloseFuture = serverConnectedChannel.close(); serverConnectedChannel = null; } if (serverChannel != null) { serverCloseFuture = serverChannel.close(); serverChannel = null; } // We must wait for the Channel cleanup to finish. In the case if the ReferenceCountedOpenSslEngineTest // the ReferenceCountedOpenSslEngine depends upon the SslContext and so we must wait the cleanup the // SslContext to avoid JVM core dumps! // // See https://github.com/netty/netty/issues/5692 if (clientCloseFuture != null) { clientCloseFuture.sync(); } if (serverConnectedCloseFuture != null) { serverConnectedCloseFuture.sync(); } if (serverCloseFuture != null) { serverCloseFuture.sync(); } if (serverSslCtx != null) { cleanupServerSslContext(serverSslCtx); serverSslCtx = null; } if (clientSslCtx != null) { cleanupClientSslContext(clientSslCtx); clientSslCtx = null; } Future serverGroupShutdownFuture = null; Future serverChildGroupShutdownFuture = null; Future clientGroupShutdownFuture = null; if (sb != null) { serverGroupShutdownFuture = sb.config().group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); serverChildGroupShutdownFuture = sb.config().childGroup().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); } if (cb != null) { clientGroupShutdownFuture = cb.config().group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); } if (serverGroupShutdownFuture != null) { serverGroupShutdownFuture.sync(); serverChildGroupShutdownFuture.sync(); } if (clientGroupShutdownFuture != null) { clientGroupShutdownFuture.sync(); } delegatingExecutor.shutdown(); serverException = null; clientException = null; } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthSameCerts(SSLEngineTestParam param) throws Throwable { mySetupMutualAuth(param, ResourcesUtil.getFile(getClass(), ""test_unencrypted.pem""), ResourcesUtil.getFile(getClass(), ""test.crt""), null); runTest(null); assertTrue(serverLatch.await(2, TimeUnit.SECONDS)); Throwable cause = serverException; if (cause != null) { throw cause; } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSetSupportedCiphers(SSLEngineTestParam param) throws Exception { if (param.protocolCipherCombo != ProtocolCipherCombo.tlsv12()) { return; } SelfSignedCertificate cert = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(cert.key(), cert.cert()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslServerProvider()).build()); final SSLEngine serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(cert.certificate()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslClientProvider()).build()); final SSLEngine clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); final String[] enabledCiphers = new String[]{ param.ciphers().get(0) }; try { clientEngine.setEnabledCipherSuites(enabledCiphers); serverEngine.setEnabledCipherSuites(enabledCiphers); assertArrayEquals(enabledCiphers, clientEngine.getEnabledCipherSuites()); assertArrayEquals(enabledCiphers, serverEngine.getEnabledCipherSuites()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); cert.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testIncompatibleCiphers(final SSLEngineTestParam param) throws Exception { assumeTrue(SslProvider.isTlsv13Supported(sslClientProvider())); assumeTrue(SslProvider.isTlsv13Supported(sslServerProvider())); SelfSignedCertificate ssc = new SelfSignedCertificate(); // Select a mandatory cipher from the TLSv1.2 RFC https://www.ietf.org/rfc/rfc5246.txt so handshakes won't fail // due to no shared/supported cipher. clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .protocols(SslProtocols.TLS_v1_3, SslProtocols.TLS_v1_2, SslProtocols.TLS_v1) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .protocols(SslProtocols.TLS_v1_3, SslProtocols.TLS_v1_2, SslProtocols.TLS_v1) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); // Set the server to only support a single TLSv1.2 cipher final String serverCipher = ""TLS_RSA_WITH_AES_128_CBC_SHA""; serverEngine.setEnabledCipherSuites(new String[] { serverCipher }); // Set the client to only support a single TLSv1.3 cipher final String clientCipher = ""TLS_AES_256_GCM_SHA384""; clientEngine.setEnabledCipherSuites(new String[] { clientCipher }); final SSLEngine client = clientEngine; final SSLEngine server = serverEngine; assertThrows(SSLHandshakeException.class, new Executable() { @Override public void execute() throws Throwable { handshake(param.type(), param.delegate(), client, server); } }); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthDiffCerts(SSLEngineTestParam param) throws Exception { File serverKeyFile = ResourcesUtil.getFile(getClass(), ""test_encrypted.pem""); File serverCrtFile = ResourcesUtil.getFile(getClass(), ""test.crt""); String serverKeyPassword = ""12345""; File clientKeyFile = ResourcesUtil.getFile(getClass(), ""test2_encrypted.pem""); File clientCrtFile = ResourcesUtil.getFile(getClass(), ""test2.crt""); String clientKeyPassword = ""12345""; mySetupMutualAuth(param, clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword, serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword); runTest(null); assertTrue(serverLatch.await(2, TimeUnit.SECONDS)); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthDiffCertsServerFailure(SSLEngineTestParam param) throws Exception { File serverKeyFile = ResourcesUtil.getFile(getClass(), ""test_encrypted.pem""); File serverCrtFile = ResourcesUtil.getFile(getClass(), ""test.crt""); String serverKeyPassword = ""12345""; File clientKeyFile = ResourcesUtil.getFile(getClass(), ""test2_encrypted.pem""); File clientCrtFile = ResourcesUtil.getFile(getClass(), ""test2.crt""); String clientKeyPassword = ""12345""; // Client trusts server but server only trusts itself mySetupMutualAuth(param, serverCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword, serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword); assertTrue(serverLatch.await(10, TimeUnit.SECONDS)); assertTrue(serverException instanceof SSLHandshakeException); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthDiffCertsClientFailure(SSLEngineTestParam param) throws Exception { File serverKeyFile = ResourcesUtil.getFile(getClass(), ""test_unencrypted.pem""); File serverCrtFile = ResourcesUtil.getFile(getClass(), ""test.crt""); String serverKeyPassword = null; File clientKeyFile = ResourcesUtil.getFile(getClass(), ""test2_unencrypted.pem""); File clientCrtFile = ResourcesUtil.getFile(getClass(), ""test2.crt""); String clientKeyPassword = null; // Server trusts client but client only trusts itself mySetupMutualAuth(param, clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword, clientCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); assertTrue(clientException instanceof SSLHandshakeException); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthInvalidClientCertSucceed(param, ClientAuth.NONE); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.OPTIONAL); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.REQUIRE); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.OPTIONAL, ""mutual_auth_client.p12"", true); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth(SSLEngineTestParam param) throws Exception { testMutualAuthClientCertFail(param, ClientAuth.REQUIRE, ""mutual_auth_client.p12"", true); } private void testMutualAuthInvalidClientCertSucceed(SSLEngineTestParam param, ClientAuth auth) throws Exception { char[] password = ""example"".toCharArray(); final KeyStore serverKeyStore = KeyStore.getInstance(""PKCS12""); serverKeyStore.load(getClass().getResourceAsStream(""mutual_auth_server.p12""), password); final KeyStore clientKeyStore = KeyStore.getInstance(""PKCS12""); clientKeyStore.load(getClass().getResourceAsStream(""mutual_auth_invalid_client.p12""), password); final KeyManagerFactory serverKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); serverKeyManagerFactory.init(serverKeyStore, password); final KeyManagerFactory clientKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); clientKeyManagerFactory.init(clientKeyStore, password); File commonCertChain = ResourcesUtil.getFile(getClass(), ""mutual_auth_ca.pem""); mySetupMutualAuth(param, serverKeyManagerFactory, commonCertChain, clientKeyManagerFactory, commonCertChain, auth, false, false); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); rethrowIfNotNull(clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); rethrowIfNotNull(serverException); } private void testMutualAuthClientCertFail(SSLEngineTestParam param, ClientAuth auth) throws Exception { testMutualAuthClientCertFail(param, auth, ""mutual_auth_invalid_client.p12"", false); } private void testMutualAuthClientCertFail(SSLEngineTestParam param, ClientAuth auth, String clientCert, boolean serverInitEngine) throws Exception { char[] password = ""example"".toCharArray(); final KeyStore serverKeyStore = KeyStore.getInstance(""PKCS12""); serverKeyStore.load(getClass().getResourceAsStream(""mutual_auth_server.p12""), password); final KeyStore clientKeyStore = KeyStore.getInstance(""PKCS12""); clientKeyStore.load(getClass().getResourceAsStream(clientCert), password); final KeyManagerFactory serverKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); serverKeyManagerFactory.init(serverKeyStore, password); final KeyManagerFactory clientKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); clientKeyManagerFactory.init(clientKeyStore, password); File commonCertChain = ResourcesUtil.getFile(getClass(), ""mutual_auth_ca.pem""); mySetupMutualAuth(param, serverKeyManagerFactory, commonCertChain, clientKeyManagerFactory, commonCertChain, auth, true, serverInitEngine); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidClientException(clientException), ""un [MASK] exception: "" + clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidServerException(serverException), ""un [MASK] exception: "" + serverException); } protected static boolean causedBySSLException(Throwable cause) { Throwable next = cause; do { if (next instanceof SSLException) { return true; } next = next.getCause(); } while (next != null); return false; } protected boolean mySetupMutualAuthServerIsValidServerException(Throwable cause) { return mySetupMutualAuthServerIsValidException(cause); } protected boolean mySetupMutualAuthServerIsValidClientException(Throwable cause) { return mySetupMutualAuthServerIsValidException(cause); } protected boolean mySetupMutualAuthServerIsValidException(Throwable cause) { // As in TLSv1.3 the handshake is sent without an extra roundtrip an SSLException is valid as well. return cause instanceof SSLException || cause instanceof ClosedChannelException; } protected void mySetupMutualAuthServerInitSslHandler(SslHandler handler) { } protected void mySetupMutualAuth(final SSLEngineTestParam param, KeyManagerFactory serverKMF, final File serverTrustManager, KeyManagerFactory clientKMF, File clientTrustManager, ClientAuth clientAuth, final boolean failureExpected, final boolean serverInitEngine) throws SSLException, InterruptedException { serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverKMF) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .trustManager(serverTrustManager) .clientAuth(clientAuth) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .protocols(param.protocols()) .ciphers(param.ciphers()) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .trustManager(clientTrustManager) .keyManager(clientKMF) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); if (serverInitEngine) { mySetupMutualAuthServerInitSslHandler(handler); } p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { if (failureExpected) { serverException = new IllegalStateException(""handshake complete. [MASK] failure""); } serverLatch.countDown(); } else if (evt instanceof SslHandshakeCompletionEvent) { serverException = ((SslHandshakeCompletionEvent) evt).cause(); serverLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { serverException = cause; ctx.fireExceptionCaught(cause); } } }); serverConnectedChannel = ch; } }); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { // With TLS1.3 a mutal auth error will not be propagated as a handshake error most of the // time as the handshake needs NO extra roundtrip. if (!failureExpected) { clientLatch.countDown(); } } else if (evt instanceof SslHandshakeCompletionEvent) { clientException = ((SslHandshakeCompletionEvent) evt).cause(); clientLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); } }); serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel(); int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port)); assertTrue(ccf.awaitUninterruptibly().isSuccess()); clientChannel = ccf.channel(); } protected static void rethrowIfNotNull(Throwable error) { if (error != null) { throw new AssertionFailedError(""Expected no error"", error); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testClientHostnameValidationSuccess(SSLEngineTestParam param) throws Exception { mySetupClientHostnameValidation(param, ResourcesUtil.getFile(getClass(), ""localhost_server.pem""), ResourcesUtil.getFile(getClass(), ""localhost_server.key""), ResourcesUtil.getFile(getClass(), ""mutual_auth_ca.pem""), false); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); rethrowIfNotNull(clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); rethrowIfNotNull(serverException); } @MethodSource(""newTestParams"") @ParameterizedTest public void testClientHostnameValidationFail(SSLEngineTestParam param) throws Exception { Future clientWriteFuture = mySetupClientHostnameValidation(param, ResourcesUtil.getFile(getClass(), ""notlocalhost_server.pem""), ResourcesUtil.getFile(getClass(), ""notlocalhost_server.key""), ResourcesUtil.getFile(getClass(), ""mutual_auth_ca.pem""), true); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidClientException(clientException), ""un [MASK] exception: "" + clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); assertTrue(mySetupMutualAuthServerIsValidServerException(serverException), ""un [MASK] exception: "" + serverException); // Verify that any pending writes are failed with the cached handshake exception and not a general SSLException. clientWriteFuture.awaitUninterruptibly(); Throwable actualCause = clientWriteFuture.cause(); assertSame(clientException, actualCause); } private Future mySetupClientHostnameValidation(final SSLEngineTestParam param, File serverCrtFile, File serverKeyFile, File clientTrustCrtFile, final boolean failureExpected) throws SSLException, InterruptedException { final String [MASK] Host = ""localhost""; serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCrtFile, serverKeyFile, null) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslContextProvider(serverSslContextProvider()) .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0) .build()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sslContextProvider(clientSslContextProvider()) .trustManager(clientTrustCrtFile) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0) .build()); serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { if (failureExpected) { serverException = new IllegalStateException(""handshake complete. [MASK] failure""); } serverLatch.countDown(); } else if (evt instanceof SslHandshakeCompletionEvent) { serverException = ((SslHandshakeCompletionEvent) evt).cause(); serverLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { serverException = cause; ctx.fireExceptionCaught(cause); } } }); serverConnectedChannel = ch; } }); final Promise clientWritePromise = ImmediateEventExecutor.INSTANCE.newPromise(); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); InetSocketAddress remoteAddress = (InetSocketAddress) serverChannel.localAddress(); SslHandler sslHandler = !param.delegate ? clientSslCtx.newHandler(ch.alloc(), [MASK] Host, 0) : clientSslCtx.newHandler(ch.alloc(), [MASK] Host, 0, delegatingExecutor); SSLParameters parameters = sslHandler.engine().getSSLParameters(); if (SslUtils.isValidHostNameForSNI( [MASK] Host)) { assertEquals(1, parameters.getServerNames().size()); assertEquals(new SNIHostName( [MASK] Host), parameters.getServerNames().get(0)); } parameters.setEndpointIdentificationAlgorithm(""HTTPS""); sslHandler.engine().setSSLParameters(parameters); p.addLast(sslHandler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void handlerAdded(ChannelHandlerContext ctx) { // Only write if there is a failure [MASK] . We don't actually care about the write going // through we just want to verify the local failure condition. This way we don't have to worry // about verifying the payload and releasing the content on the server side. if (failureExpected) { ChannelFuture f = ctx.write(ctx.alloc().buffer(1).writeByte(1)); PromiseNotifier.cascade(f, clientWritePromise); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { if (failureExpected) { clientException = new IllegalStateException(""handshake complete. [MASK] failure""); } clientLatch.countDown(); } else if (evt instanceof SslHandshakeCompletionEvent) { clientException = ((SslHandshakeCompletionEvent) evt).cause(); clientLatch.countDown(); } ctx.fireUserEventTriggered(evt); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); } }); serverChannel = sb.bind(new InetSocketAddress( [MASK] Host, 0)).sync().channel(); final int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); ChannelFuture ccf = cb.connect(new InetSocketAddress( [MASK] Host, port)); assertTrue(ccf.awaitUninterruptibly().isSuccess()); clientChannel = ccf.channel(); return clientWritePromise; } private void mySetupMutualAuth(SSLEngineTestParam param, File keyFile, File crtFile, String keyPassword) throws SSLException, InterruptedException { mySetupMutualAuth(param, crtFile, keyFile, crtFile, keyPassword, crtFile, keyFile, crtFile, keyPassword); } private void verifySSLSessionForMutualAuth( SSLEngineTestParam param, SSLSession session, File certFile, String principalName) throws Exception { InputStream in = null; try { assertEquals(principalName, session.getLocalPrincipal().getName()); assertEquals(principalName, session.getPeerPrincipal().getName()); assertNotNull(session.getId()); assertEquals(param.combo().cipher, session.getCipherSuite()); assertEquals(param.combo().protocol, session.getProtocol()); assertTrue(session.getApplicationBufferSize() > 0); assertTrue(session.getCreationTime() > 0); assertTrue(session.isValid()); assertTrue(session.getLastAccessedTime() > 0); in = new FileInputStream(certFile); final byte[] certBytes = SslContext.X509_CERT_FACTORY .generateCertificate(in).getEncoded(); // Verify session assertEquals(1, session.getPeerCertificates().length); assertArrayEquals(certBytes, session.getPeerCertificates()[0].getEncoded()); try { assertEquals(1, session.getPeerCertificateChain().length); assertArrayEquals(certBytes, session.getPeerCertificateChain()[0].getEncoded()); } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } assertEquals(1, session.getLocalCertificates().length); assertArrayEquals(certBytes, session.getLocalCertificates()[0].getEncoded()); } finally { if (in != null) { in.close(); } } } private void mySetupMutualAuth(final SSLEngineTestParam param, File servertTrustCrtFile, File serverKeyFile, final File serverCrtFile, String serverKeyPassword, File clientTrustCrtFile, File clientKeyFile, final File clientCrtFile, String clientKeyPassword) throws InterruptedException, SSLException { serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCrtFile, serverKeyFile, serverKeyPassword) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .trustManager(servertTrustCrtFile) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .trustManager(clientTrustCrtFile) .keyManager(clientCrtFile, clientKeyFile, clientKeyPassword) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0).build()); serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); final SSLEngine engine = wrapEngine(serverSslCtx.newEngine(ch.alloc())); engine.setUseClientMode(false); engine.setNeedClientAuth(true); p.addLast(new SslHandler(engine)); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { serverException = cause; ctx.fireExceptionCaught(cause); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { try { verifySSLSessionForMutualAuth( param, engine.getSession(), serverCrtFile, PRINCIPAL_NAME); } catch (Throwable cause) { serverException = cause; } } } }); serverConnectedChannel = ch; } }); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); final SslHandler handler = !param.delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); handler.engine().setNeedClientAuth(true); ChannelPipeline p = ch.pipeline(); p.addLast(handler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == SslHandshakeCompletionEvent.SUCCESS) { try { verifySSLSessionForMutualAuth( param, handler.engine().getSession(), clientCrtFile, PRINCIPAL_NAME); } catch (Throwable cause) { clientException = cause; } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); } }); serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel(); int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port)); assertTrue(ccf.awaitUninterruptibly().isSuccess()); clientChannel = ccf.channel(); } protected void runTest(String [MASK] ApplicationProtocol) throws Exception { final ByteBuf clientMessage = Unpooled.copiedBuffer(""I am a client"".getBytes()); final ByteBuf serverMessage = Unpooled.copiedBuffer(""I am a server"".getBytes()); try { writeAndVerifyReceived(clientMessage.retain(), clientChannel, serverLatch, serverReceiver); writeAndVerifyReceived(serverMessage.retain(), serverConnectedChannel, clientLatch, clientReceiver); verifyApplicationLevelProtocol(clientChannel, [MASK] ApplicationProtocol); verifyApplicationLevelProtocol(serverConnectedChannel, [MASK] ApplicationProtocol); } finally { clientMessage.release(); serverMessage.release(); } } private static void verifyApplicationLevelProtocol(Channel channel, String [MASK] ApplicationProtocol) { SslHandler handler = channel.pipeline().get(SslHandler.class); assertNotNull(handler); String appProto = handler.applicationProtocol(); assertEquals( [MASK] ApplicationProtocol, appProto); SSLEngine engine = handler.engine(); if (engine instanceof JdkAlpnSslEngine) { // Also verify the Java9 exposed method. JdkAlpnSslEngine java9SslEngine = (JdkAlpnSslEngine) engine; assertEquals( [MASK] ApplicationProtocol == null ? StringUtil.EMPTY_STRING : [MASK] ApplicationProtocol, java9SslEngine.getApplicationProtocol()); } } private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch, MessageReceiver receiver) throws Exception { List dataCapture = null; try { assertTrue(sendChannel.writeAndFlush(message).await(10, TimeUnit.SECONDS)); receiverLatch.await(5, TimeUnit.SECONDS); message.resetReaderIndex(); ArgumentCaptor captor = ArgumentCaptor.forClass(ByteBuf.class); verify(receiver).messageReceived(captor.capture()); dataCapture = captor.getAllValues(); assertEquals(message, dataCapture.get(0)); } finally { if (dataCapture != null) { for (ByteBuf data : dataCapture) { data.release(); } } } } @Test public void testGetCreationTime() throws Exception { clientSslCtx = wrapContext(null, SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()).build()); SSLEngine engine = null; try { engine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); assertTrue(engine.getSession().getCreationTime() <= System.currentTimeMillis()); } finally { cleanupClientSslEngine(engine); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionInvalidate(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession session = serverEngine.getSession(); assertTrue(session.isValid()); session.invalidate(); assertFalse(session.isValid()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSSLSessionId(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) // This test only works for non TLSv1.3 for now .protocols(param.protocols()) .sslContextProvider(clientSslContextProvider()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) // This test only works for non TLSv1.3 for now .protocols(param.protocols()) .sslContextProvider(serverSslContextProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); // Before the handshake the id should have length == 0 assertEquals(0, clientEngine.getSession().getId().length); assertEquals(0, serverEngine.getSession().getId().length); handshake(param.type(), param.delegate(), clientEngine, serverEngine); if (param.protocolCipherCombo == ProtocolCipherCombo.TLSV13) { // Allocate something which is big enough for sure ByteBuffer packetBuffer = allocateBuffer(param.type(), 32 * 1024); ByteBuffer appBuffer = allocateBuffer(param.type(), 32 * 1024); appBuffer.clear().position(4).flip(); packetBuffer.clear(); do { SSLEngineResult result; do { result = serverEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = clientEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); do { result = clientEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = serverEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); } while (clientEngine.getSession().getId().length == 0); // With TLS1.3 we should see pseudo IDs and so these should never match. assertFalse(Arrays.equals(clientEngine.getSession().getId(), serverEngine.getSession().getId())); } else { // After the handshake the id should have length > 0 assertNotEquals(0, clientEngine.getSession().getId().length); assertNotEquals(0, serverEngine.getSession().getId().length); assertArrayEquals(clientEngine.getSession().getId(), serverEngine.getSession().getId()); } } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest @Timeout(30) public void clientInitiatedRenegotiationWithFatalAlertDoesNotInfiniteLoopServer(final SSLEngineTestParam param) throws Exception { assumeTrue(PlatformDependent.javaVersion() >= 11); final SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); sb = new ServerBootstrap() .group(new NioEventLoopGroup(1)) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type)); ChannelPipeline p = ch.pipeline(); SslHandler handler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(handler); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof SslHandshakeCompletionEvent && ((SslHandshakeCompletionEvent) evt).isSuccess()) { // This data will be sent to the client before any of the re-negotiation data can be // sent. The client will read this, detect that it is not the response to // renegotiation which was [MASK] , and respond with a fatal alert. ctx.writeAndFlush(ctx.alloc().buffer(1).writeByte(100)); } ctx.fireUserEventTriggered(evt); } @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) { ReferenceCountUtil.release(msg); // The server then attempts to trigger a flush operation once the application data is // received from the client. The flush will encrypt all data and should not result in // deadlock. ctx.channel().eventLoop().schedule(new Runnable() { @Override public void run() { ctx.writeAndFlush(ctx.alloc().buffer(1).writeByte(101)); } }, 500, TimeUnit.MILLISECONDS); } @Override public void channelInactive(ChannelHandlerContext ctx) { serverLatch.countDown(); } }); serverConnectedChannel = ch; } }); serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() // OpenSslEngine doesn't support renegotiation on client side .sslProvider(SslProvider.JDK) .trustManager(InsecureTrustManagerFactory.INSTANCE) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); cb = new Bootstrap(); cb.group(new NioEventLoopGroup(1)) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); ChannelPipeline p = ch.pipeline(); SslHandler sslHandler = !param.delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); // The renegotiate is not [MASK] to succeed, so we should stop trying in a timely manner so // the unit test can terminate relativley quicly. sslHandler.setHandshakeTimeout(1, TimeUnit.SECONDS); p.addLast(sslHandler); p.addLast(new ChannelInboundHandlerAdapter() { private int handshakeCount; @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { // OpenSSL SSLEngine sends a fatal alert for the renegotiation handshake because the // user data read as part of the handshake. The client receives this fatal alert and is // [MASK] to shutdown the connection. The ""invalid data"" during the renegotiation // handshake is also delivered to channelRead(..) on the server. // JDK SSLEngine completes the renegotiation handshake and delivers the ""invalid data"" // is also delivered to channelRead(..) on the server. JDK SSLEngine does not send a // fatal error and so for testing purposes we close the connection after we have // completed the first renegotiation handshake (which is the second handshake). if (evt instanceof SslHandshakeCompletionEvent && ++handshakeCount == 2) { ctx.close(); return; } ctx.fireUserEventTriggered(evt); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ReferenceCountUtil.release(msg); // Simulate a request that the server's application logic will think is invalid. ctx.writeAndFlush(ctx.alloc().buffer(1).writeByte(102)); ctx.pipeline().get(SslHandler.class).renegotiate(); } }); } }); ChannelFuture ccf = cb.connect(serverChannel.localAddress()); assertTrue(ccf.syncUninterruptibly().isSuccess()); clientChannel = ccf.channel(); serverLatch.await(); ssc.delete(); } protected void testEnablingAnAlreadyDisabledSslProtocol(SSLEngineTestParam param, String[] protocols1, String[] protocols2) throws Exception { SSLEngine sslEngine = null; try { File serverKeyFile = ResourcesUtil.getFile(getClass(), ""test_unencrypted.pem""); File serverCrtFile = ResourcesUtil.getFile(getClass(), ""test.crt""); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCrtFile, serverKeyFile) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); sslEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); // Disable all protocols sslEngine.setEnabledProtocols(EmptyArrays.EMPTY_STRINGS); // The only protocol that should be enabled is SSLv2Hello String[] enabledProtocols = sslEngine.getEnabledProtocols(); assertArrayEquals(protocols1, enabledProtocols); // Enable a protocol that is currently disabled sslEngine.setEnabledProtocols(new String[]{ SslProtocols.TLS_v1_2 }); // The protocol that was just enabled should be returned enabledProtocols = sslEngine.getEnabledProtocols(); assertEquals(protocols2.length, enabledProtocols.length); assertArrayEquals(protocols2, enabledProtocols); } finally { if (sslEngine != null) { sslEngine.closeInbound(); sslEngine.closeOutbound(); cleanupServerSslEngine(sslEngine); } } } protected void handshake(BufferType type, boolean delegate, SSLEngine clientEngine, SSLEngine serverEngine) throws Exception { ByteBuffer cTOs = allocateBuffer(type, clientEngine.getSession().getPacketBufferSize()); ByteBuffer sTOc = allocateBuffer(type, serverEngine.getSession().getPacketBufferSize()); ByteBuffer serverAppReadBuffer = allocateBuffer(type, serverEngine.getSession().getApplicationBufferSize()); ByteBuffer clientAppReadBuffer = allocateBuffer(type, clientEngine.getSession().getApplicationBufferSize()); clientEngine.beginHandshake(); serverEngine.beginHandshake(); ByteBuffer empty = allocateBuffer(type, 0); SSLEngineResult clientResult; SSLEngineResult serverResult; boolean clientHandshakeFinished = false; boolean serverHandshakeFinished = false; boolean cTOsHasRemaining; boolean sTOcHasRemaining; do { int cTOsPos = cTOs.position(); int sTOcPos = sTOc.position(); if (!clientHandshakeFinished) { clientResult = clientEngine.wrap(empty, cTOs); runDelegatedTasks(delegate, clientResult, clientEngine); assertEquals(empty.remaining(), clientResult.bytesConsumed()); assertEquals(cTOs.position() - cTOsPos, clientResult.bytesProduced()); if (isHandshakeFinished(clientResult)) { clientHandshakeFinished = true; } if (clientResult.getStatus() == Status.BUFFER_OVERFLOW) { cTOs = increaseDstBuffer(clientEngine.getSession().getPacketBufferSize(), type, cTOs); } } if (!serverHandshakeFinished) { serverResult = serverEngine.wrap(empty, sTOc); runDelegatedTasks(delegate, serverResult, serverEngine); assertEquals(empty.remaining(), serverResult.bytesConsumed()); assertEquals(sTOc.position() - sTOcPos, serverResult.bytesProduced()); if (isHandshakeFinished(serverResult)) { serverHandshakeFinished = true; } if (serverResult.getStatus() == Status.BUFFER_OVERFLOW) { sTOc = increaseDstBuffer(serverEngine.getSession().getPacketBufferSize(), type, sTOc); } } cTOs.flip(); sTOc.flip(); cTOsPos = cTOs.position(); sTOcPos = sTOc.position(); if (!clientHandshakeFinished || // After the handshake completes it is possible we have more data that was send by the server as // the server will send session updates after the handshake. In this case continue to unwrap. SslProtocols.TLS_v1_3.equals(clientEngine.getSession().getProtocol())) { int clientAppReadBufferPos = clientAppReadBuffer.position(); clientResult = clientEngine.unwrap(sTOc, clientAppReadBuffer); runDelegatedTasks(delegate, clientResult, clientEngine); assertEquals(sTOc.position() - sTOcPos, clientResult.bytesConsumed()); assertEquals(clientAppReadBuffer.position() - clientAppReadBufferPos, clientResult.bytesProduced()); assertEquals(0, clientAppReadBuffer.position()); if (isHandshakeFinished(clientResult)) { clientHandshakeFinished = true; } if (clientResult.getStatus() == Status.BUFFER_OVERFLOW) { clientAppReadBuffer = increaseDstBuffer( clientEngine.getSession().getApplicationBufferSize(), type, clientAppReadBuffer); } } else { assertEquals(0, sTOc.remaining()); } if (!serverHandshakeFinished) { int serverAppReadBufferPos = serverAppReadBuffer.position(); serverResult = serverEngine.unwrap(cTOs, serverAppReadBuffer); runDelegatedTasks(delegate, serverResult, serverEngine); assertEquals(cTOs.position() - cTOsPos, serverResult.bytesConsumed()); assertEquals(serverAppReadBuffer.position() - serverAppReadBufferPos, serverResult.bytesProduced()); assertEquals(0, serverAppReadBuffer.position()); if (isHandshakeFinished(serverResult)) { serverHandshakeFinished = true; } if (serverResult.getStatus() == Status.BUFFER_OVERFLOW) { serverAppReadBuffer = increaseDstBuffer( serverEngine.getSession().getApplicationBufferSize(), type, serverAppReadBuffer); } } else { assertFalse(cTOs.hasRemaining()); } cTOsHasRemaining = compactOrClear(cTOs); sTOcHasRemaining = compactOrClear(sTOc); serverAppReadBuffer.clear(); clientAppReadBuffer.clear(); if (clientEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { clientHandshakeFinished = true; } if (serverEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { serverHandshakeFinished = true; } } while (!clientHandshakeFinished || !serverHandshakeFinished || // We need to ensure we feed all the data to the engine to not end up with a corrupted state. // This is especially important with TLS1.3 which may produce sessions after the ""main handshake"" is // done cTOsHasRemaining || sTOcHasRemaining); } private static boolean compactOrClear(ByteBuffer buffer) { if (buffer.hasRemaining()) { buffer.compact(); return true; } buffer.clear(); return false; } private ByteBuffer increaseDstBuffer(int maxBufferSize, BufferType type, ByteBuffer dstBuffer) { assumeFalse(maxBufferSize == dstBuffer.remaining()); // We need to increase the destination buffer dstBuffer.flip(); ByteBuffer tmpBuffer = allocateBuffer(type, maxBufferSize + dstBuffer.remaining()); tmpBuffer.put(dstBuffer); return tmpBuffer; } private static boolean isHandshakeFinished(SSLEngineResult result) { return result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED; } private void runDelegatedTasks(boolean delegate, SSLEngineResult result, SSLEngine engine) { if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) { for (;;) { Runnable task = engine.getDelegatedTask(); if (task == null) { break; } if (!delegate) { task.run(); } else { delegatingExecutor.execute(task); } } } } protected abstract SslProvider sslClientProvider(); protected abstract SslProvider sslServerProvider(); protected Provider clientSslContextProvider() { return null; } protected Provider serverSslContextProvider() { return null; } /** * Called from the test cleanup code and can be used to release the {@code ctx} if it must be done manually. */ protected void cleanupClientSslContext(SslContext ctx) { } /** * Called from the test cleanup code and can be used to release the {@code ctx} if it must be done manually. */ protected void cleanupServerSslContext(SslContext ctx) { } /** * Called when ever an SSLEngine is not wrapped by a {@link SslHandler} and inserted into a pipeline. */ protected void cleanupClientSslEngine(SSLEngine engine) { } /** * Called when ever an SSLEngine is not wrapped by a {@link SslHandler} and inserted into a pipeline. */ protected void cleanupServerSslEngine(SSLEngine engine) { } protected void setupHandlers(SSLEngineTestParam param, ApplicationProtocolConfig apn) throws InterruptedException, SSLException, CertificateException { setupHandlers(param, apn, apn); } protected void setupHandlers(SSLEngineTestParam param, ApplicationProtocolConfig serverApn, ApplicationProtocolConfig clientApn) throws InterruptedException, SSLException, CertificateException { SelfSignedCertificate ssc = new SelfSignedCertificate(); try { SslContextBuilder serverCtxBuilder = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey(), null) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .applicationProtocolConfig(serverApn) .sessionCacheSize(0) .sessionTimeout(0); if (serverApn.protocol() == Protocol.NPN || serverApn.protocol() == Protocol.NPN_AND_ALPN) { // NPN is not really well supported with TLSv1.3 so force to use TLSv1.2 // See https://github.com/openssl/openssl/issues/3665 serverCtxBuilder.protocols(SslProtocols.TLS_v1_2); } SslContextBuilder clientCtxBuilder = SslContextBuilder.forClient() .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .applicationProtocolConfig(clientApn) .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(null, IdentityCipherSuiteFilter.INSTANCE) .sessionCacheSize(0) .sessionTimeout(0); if (clientApn.protocol() == Protocol.NPN || clientApn.protocol() == Protocol.NPN_AND_ALPN) { // NPN is not really well supported with TLSv1.3 so force to use TLSv1.2 // See https://github.com/openssl/openssl/issues/3665 clientCtxBuilder.protocols(SslProtocols.TLS_v1_2); } setupHandlers(param.type(), param.delegate(), wrapContext(param, serverCtxBuilder.build()), wrapContext(param, clientCtxBuilder.build())); } finally { ssc.delete(); } } protected void setupHandlers(final BufferType type, final boolean delegate, SslContext serverCtx, SslContext clientCtx) throws InterruptedException, SSLException, CertificateException { serverSslCtx = serverCtx; clientSslCtx = clientCtx; serverConnectedChannel = null; sb = new ServerBootstrap(); cb = new Bootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), type)); ChannelPipeline p = ch.pipeline(); SslHandler sslHandler = !delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(sslHandler); p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { serverException = cause.getCause(); serverLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } }); serverConnectedChannel = ch; } }); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), type)); ChannelPipeline p = ch.pipeline(); SslHandler sslHandler = !delegate ? clientSslCtx.newHandler(ch.alloc()) : clientSslCtx.newHandler(ch.alloc(), delegatingExecutor); p.addLast(sslHandler); p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch)); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause.getCause() instanceof SSLHandshakeException) { clientException = cause.getCause(); clientLatch.countDown(); } else { ctx.fireExceptionCaught(cause); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { clientLatch.countDown(); } }); } }); serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); ChannelFuture ccf = cb.connect(serverChannel.localAddress()); assertTrue(ccf.syncUninterruptibly().isSuccess()); clientChannel = ccf.channel(); } @MethodSource(""newTestParams"") @ParameterizedTest @Timeout(30) public void testMutualAuthSameCertChain(final SSLEngineTestParam param) throws Exception { SelfSignedCertificate serverCert = new SelfSignedCertificate(); SelfSignedCertificate clientCert = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(serverCert.certificate(), serverCert.privateKey()) .trustManager(clientCert.cert()) .clientAuth(ClientAuth.REQUIRE).sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()).build()); sb = new ServerBootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); final Promise promise = sb.config().group().next().newPromise(); serverChannel = sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); SslHandler sslHandler = !param.delegate ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); ch.pipeline().addFirst(sslHandler); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof SslHandshakeCompletionEvent) { Throwable cause = ((SslHandshakeCompletionEvent) evt).cause(); if (cause == null) { SSLSession session = ((SslHandler) ctx.pipeline().first()).engine().getSession(); Certificate[] peerCertificates = session.getPeerCertificates(); if (peerCertificates == null) { promise.setFailure(new NullPointerException(""peerCertificates"")); return; } try { X509Certificate[] peerCertificateChain = session.getPeerCertificateChain(); if (peerCertificateChain == null) { promise.setFailure(new NullPointerException(""peerCertificateChain"")); } else if (peerCertificateChain.length + peerCertificates.length != 4) { String excTxtFmt = ""peerCertificateChain.length:%s, peerCertificates.length:%s""; promise.setFailure(new IllegalStateException(String.format(excTxtFmt, peerCertificateChain.length, peerCertificates.length))); } else { for (int i = 0; i < peerCertificateChain.length; i++) { if (peerCertificateChain[i] == null || peerCertificates[i] == null) { promise.setFailure( new IllegalStateException(""Certificate in chain is null"")); return; } } promise.setSuccess(null); } } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); assertEquals(2, peerCertificates.length); for (int i = 0; i < peerCertificates.length; i++) { if (peerCertificates[i] == null) { promise.setFailure( new IllegalStateException(""Certificate in chain is null"")); return; } } promise.setSuccess(null); } } else { promise.setFailure(cause); } } } }); serverConnectedChannel = ch; } }).bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); // We create a new chain for certificates which contains 2 certificates ByteArrayOutputStream chainStream = new ByteArrayOutputStream(); chainStream.write(Files.readAllBytes(clientCert.certificate().toPath())); chainStream.write(Files.readAllBytes(serverCert.certificate().toPath())); clientSslCtx = wrapContext(param, SslContextBuilder.forClient().keyManager( new ByteArrayInputStream(chainStream.toByteArray()), new FileInputStream(clientCert.privateKey())) .trustManager(new FileInputStream(serverCert.certificate())) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()).ciphers(param.ciphers()).build()); cb = new Bootstrap(); cb.group(new NioEventLoopGroup()); cb.channel(NioSocketChannel.class); clientChannel = cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); ch.pipeline().addLast(new SslHandler(wrapEngine(clientSslCtx.newEngine(ch.alloc())))); } }).connect(serverChannel.localAddress()).syncUninterruptibly().channel(); promise.syncUninterruptibly(); serverCert.delete(); clientCert.delete(); } @MethodSource(""newTestParams"") @ParameterizedTest public void testUnwrapBehavior(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); byte[] bytes = ""Hello World"".getBytes(CharsetUtil.US_ASCII); try { ByteBuffer plainClientOut = allocateBuffer(param.type, client.getSession().getApplicationBufferSize()); ByteBuffer encryptedClientToServer = allocateBuffer( param.type, server.getSession().getPacketBufferSize() * 2); ByteBuffer plainServerIn = allocateBuffer(param.type, server.getSession().getApplicationBufferSize()); handshake(param.type(), param.delegate(), client, server); // create two TLS frames // first frame plainClientOut.put(bytes, 0, 5); plainClientOut.flip(); SSLEngineResult result = client.wrap(plainClientOut, encryptedClientToServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(5, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); assertFalse(plainClientOut.hasRemaining()); // second frame plainClientOut.clear(); plainClientOut.put(bytes, 5, 6); plainClientOut.flip(); result = client.wrap(plainClientOut, encryptedClientToServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(6, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); // send over to server encryptedClientToServer.flip(); // try with too small output buffer first (to check BUFFER_OVERFLOW case) int remaining = encryptedClientToServer.remaining(); ByteBuffer small = allocateBuffer(param.type, 3); result = server.unwrap(encryptedClientToServer, small); assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); assertEquals(remaining, encryptedClientToServer.remaining()); // now with big enough buffer result = server.unwrap(encryptedClientToServer, plainServerIn); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(5, result.bytesProduced()); assertTrue(encryptedClientToServer.hasRemaining()); result = server.unwrap(encryptedClientToServer, plainServerIn); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(6, result.bytesProduced()); assertFalse(encryptedClientToServer.hasRemaining()); plainServerIn.flip(); assertEquals(ByteBuffer.wrap(bytes), plainServerIn); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testProtocolMatch(SSLEngineTestParam param) throws Exception { testProtocol(param, false, new String[] {""TLSv1.2""}, new String[] {""TLSv1"", ""TLSv1.1"", ""TLSv1.2""}); } @MethodSource(""newTestParams"") @ParameterizedTest public void testProtocolNoMatch(SSLEngineTestParam param) throws Exception { testProtocol(param, true, new String[] {""TLSv1.2""}, new String[] {""TLSv1"", ""TLSv1.1""}); } private void testProtocol(final SSLEngineTestParam param, boolean handshakeFails, String[] clientProtocols, String[] serverProtocols) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslProvider(sslClientProvider()) .protocols(clientProtocols) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslProvider(sslServerProvider()) .protocols(serverProtocols) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { if (handshakeFails) { final SSLEngine clientEngine = client; final SSLEngine serverEngine = server; assertThrows(SSLHandshakeException.class, new Executable() { @Override public void execute() throws Throwable { handshake(param.type(), param.delegate(), clientEngine, serverEngine); } }); } else { handshake(param.type(), param.delegate(), client, server); } } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } private String[] nonContiguousProtocols(SslProvider provider) { if (provider != null) { // conscrypt not correctly filters out TLSv1 and TLSv1.1 which is required now by the JDK. // https://github.com/google/conscrypt/issues/1013 return new String[] { SslProtocols.TLS_v1_2 }; } return new String[] {SslProtocols.TLS_v1_2, SslProtocols.TLS_v1}; } @MethodSource(""newTestParams"") @ParameterizedTest public void testHandshakeCompletesWithNonContiguousProtocolsTLSv1_2CipherOnly(SSLEngineTestParam param) throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); // Select a mandatory cipher from the TLSv1.2 RFC https://www.ietf.org/rfc/rfc5246.txt so handshakes won't fail // due to no shared/supported cipher. final String sharedCipher = ""TLS_RSA_WITH_AES_128_CBC_SHA""; clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(Collections.singletonList(sharedCipher)) .protocols(nonContiguousProtocols(sslClientProvider())) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .ciphers(Collections.singletonList(sharedCipher)) .protocols(nonContiguousProtocols(sslServerProvider())) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testHandshakeCompletesWithoutFilteringSupportedCipher(SSLEngineTestParam param) throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); // Select a mandatory cipher from the TLSv1.2 RFC https://www.ietf.org/rfc/rfc5246.txt so handshakes won't fail // due to no shared/supported cipher. final String sharedCipher = ""TLS_RSA_WITH_AES_128_CBC_SHA""; clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .ciphers(Collections.singletonList(sharedCipher), SupportedCipherSuiteFilter.INSTANCE) .protocols(nonContiguousProtocols(sslClientProvider())) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .ciphers(Collections.singletonList(sharedCipher), SupportedCipherSuiteFilter.INSTANCE) .protocols(nonContiguousProtocols(sslServerProvider())) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testPacketBufferSizeLimit(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { // Allocate an buffer that is bigger then the max plain record size. ByteBuffer plainServerOut = allocateBuffer( param.type(), server.getSession().getApplicationBufferSize() * 2); handshake(param.type(), param.delegate(), client, server); // Fill the whole buffer and flip it. plainServerOut.position(plainServerOut.capacity()); plainServerOut.flip(); ByteBuffer encryptedServerToClient = allocateBuffer( param.type(), server.getSession().getPacketBufferSize()); int encryptedServerToClientPos = encryptedServerToClient.position(); int plainServerOutPos = plainServerOut.position(); SSLEngineResult result = server.wrap(plainServerOut, encryptedServerToClient); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(plainServerOut.position() - plainServerOutPos, result.bytesConsumed()); assertEquals(encryptedServerToClient.position() - encryptedServerToClientPos, result.bytesProduced()); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSSLEngineUnwrapNoSslRecord(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); final SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { final ByteBuffer src = allocateBuffer(param.type(), client.getSession().getApplicationBufferSize()); final ByteBuffer dst = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer empty = allocateBuffer(param.type(), 0); SSLEngineResult clientResult = client.wrap(empty, dst); assertEquals(SSLEngineResult.Status.OK, clientResult.getStatus()); assertEquals(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, clientResult.getHandshakeStatus()); assertThrows(SSLException.class, new Executable() { @Override public void execute() throws Throwable { client.unwrap(src, dst); } }); } finally { cleanupClientSslEngine(client); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testBeginHandshakeAfterEngineClosed(SSLEngineTestParam param) throws SSLException { clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { client.closeInbound(); client.closeOutbound(); try { client.beginHandshake(); fail(); } catch (SSLException [MASK] ) { // [MASK] } catch (IllegalStateException e) { if (!Conscrypt.isEngineSupported(client)) { throw e; } // Workaround for conscrypt bug // See https://github.com/google/conscrypt/issues/840 } } finally { cleanupClientSslEngine(client); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testBeginHandshakeCloseOutbound(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { testBeginHandshakeCloseOutbound(param, client); testBeginHandshakeCloseOutbound(param, server); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } private void testBeginHandshakeCloseOutbound(SSLEngineTestParam param, SSLEngine engine) throws SSLException { ByteBuffer dst = allocateBuffer(param.type(), engine.getSession().getPacketBufferSize()); ByteBuffer empty = allocateBuffer(param.type(), 0); engine.beginHandshake(); engine.closeOutbound(); SSLEngineResult result; for (;;) { result = engine.wrap(empty, dst); dst.flip(); assertEquals(0, result.bytesConsumed()); assertEquals(dst.remaining(), result.bytesProduced()); if (result.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NEED_WRAP) { break; } dst.clear(); } assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); } @MethodSource(""newTestParams"") @ParameterizedTest public void testCloseInboundAfterBeginHandshake(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { testCloseInboundAfterBeginHandshake(client); testCloseInboundAfterBeginHandshake(server); } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } private static void testCloseInboundAfterBeginHandshake(SSLEngine engine) throws SSLException { engine.beginHandshake(); try { engine.closeInbound(); // Workaround for conscrypt bug // See https://github.com/google/conscrypt/issues/839 if (!Conscrypt.isEngineSupported(engine)) { fail(); } } catch (SSLException [MASK] ) { // [MASK] } } @MethodSource(""newTestParams"") @ParameterizedTest public void testCloseNotifySequence(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) // This test only works for non TLSv1.3 for now .protocols(SslProtocols.TLS_v1_2) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) // This test only works for non TLSv1.3 for now .protocols(SslProtocols.TLS_v1_2) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainClientOut = allocateBuffer(param.type(), client.getSession().getApplicationBufferSize()); ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); ByteBuffer encryptedClientToServer = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer encryptedServerToClient = allocateBuffer(param.type(), server.getSession().getPacketBufferSize()); ByteBuffer empty = allocateBuffer(param.type(), 0); handshake(param.type(), param.delegate(), client, server); // This will produce a close_notify client.closeOutbound(); // Something still pending in the outbound buffer. assertFalse(client.isOutboundDone()); assertFalse(client.isInboundDone()); // Now wrap and so drain the outbound buffer. SSLEngineResult result = client.wrap(empty, encryptedClientToServer); encryptedClientToServer.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); SSLEngineResult.HandshakeStatus hs = result.getHandshakeStatus(); // Need an UNWRAP to read the response of the close_notify if (sslClientProvider() == SslProvider.JDK || Conscrypt.isEngineSupported(client)) { assertTrue(hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING || hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP); } else { assertEquals(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, hs); } int produced = result.bytesProduced(); int consumed = result.bytesConsumed(); int closeNotifyLen = produced; assertTrue(produced > 0); assertEquals(0, consumed); assertEquals(produced, encryptedClientToServer.remaining()); // Outbound buffer should be drained now. assertTrue(client.isOutboundDone()); assertFalse(client.isInboundDone()); assertFalse(server.isOutboundDone()); assertFalse(server.isInboundDone()); result = server.unwrap(encryptedClientToServer, plainServerOut); plainServerOut.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); // Need a WRAP to respond to the close_notify assertEquals(SSLEngineResult.HandshakeStatus.NEED_WRAP, result.getHandshakeStatus()); produced = result.bytesProduced(); consumed = result.bytesConsumed(); assertEquals(closeNotifyLen, consumed); assertEquals(0, produced); // Should have consumed the complete close_notify assertEquals(0, encryptedClientToServer.remaining()); assertEquals(0, plainServerOut.remaining()); assertFalse(server.isOutboundDone()); assertTrue(server.isInboundDone()); result = server.wrap(empty, encryptedServerToClient); encryptedServerToClient.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); // UNWRAP/WRAP are not [MASK] after this point assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus()); produced = result.bytesProduced(); consumed = result.bytesConsumed(); assertEquals(closeNotifyLen, produced); assertEquals(0, consumed); assertEquals(produced, encryptedServerToClient.remaining()); assertTrue(server.isOutboundDone()); assertTrue(server.isInboundDone()); result = client.unwrap(encryptedServerToClient, plainClientOut); plainClientOut.flip(); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); // UNWRAP/WRAP are not [MASK] after this point assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus()); produced = result.bytesProduced(); consumed = result.bytesConsumed(); assertEquals(closeNotifyLen, consumed); assertEquals(0, produced); assertEquals(0, encryptedServerToClient.remaining()); assertTrue(client.isOutboundDone()); assertTrue(client.isInboundDone()); // Ensure that calling wrap or unwrap again will not produce an SSLException encryptedServerToClient.clear(); plainServerOut.clear(); result = server.wrap(plainServerOut, encryptedServerToClient); assertEngineRemainsClosed(result); encryptedClientToServer.clear(); plainServerOut.clear(); result = server.unwrap(encryptedClientToServer, plainServerOut); assertEngineRemainsClosed(result); encryptedClientToServer.clear(); plainClientOut.clear(); result = client.wrap(plainClientOut, encryptedClientToServer); assertEngineRemainsClosed(result); encryptedServerToClient.clear(); plainClientOut.clear(); result = client.unwrap(encryptedServerToClient, plainClientOut); assertEngineRemainsClosed(result); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } private static void assertEngineRemainsClosed(SSLEngineResult result) { assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus()); assertEquals(0, result.bytesConsumed()); assertEquals(0, result.bytesProduced()); } @MethodSource(""newTestParams"") @ParameterizedTest public void testWrapAfterCloseOutbound(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer dst = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer src = allocateBuffer(param.type(), 1024); handshake(param.type(), param.delegate(), client, server); // This will produce a close_notify client.closeOutbound(); SSLEngineResult result = client.wrap(src, dst); assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus()); assertEquals(0, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); assertTrue(client.isOutboundDone()); assertFalse(client.isInboundDone()); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testMultipleRecordsInOneBufferWithNonZeroPosition(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { // Choose buffer size small enough that we can put multiple buffers into one buffer and pass it into the // unwrap call without exceed MAX_ENCRYPTED_PACKET_LENGTH. ByteBuffer plainClientOut = allocateBuffer(param.type(), 1024); ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); ByteBuffer encClientToServer = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); int positionOffset = 1; // We need to be able to hold 2 records + positionOffset ByteBuffer combinedEncClientToServer = allocateBuffer( param.type(), encClientToServer.capacity() * 2 + positionOffset); combinedEncClientToServer.position(positionOffset); handshake(param.type(), param.delegate(), client, server); plainClientOut.limit(plainClientOut.capacity()); SSLEngineResult result = client.wrap(plainClientOut, encClientToServer); assertEquals(plainClientOut.capacity(), result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); encClientToServer.flip(); // Copy the first record into the combined buffer combinedEncClientToServer.put(encClientToServer); plainClientOut.clear(); encClientToServer.clear(); result = client.wrap(plainClientOut, encClientToServer); assertEquals(plainClientOut.capacity(), result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); encClientToServer.flip(); int encClientToServerLen = encClientToServer.remaining(); // Copy the first record into the combined buffer combinedEncClientToServer.put(encClientToServer); encClientToServer.clear(); combinedEncClientToServer.flip(); combinedEncClientToServer.position(positionOffset); // Ensure we have the first record and a tiny amount of the second record in the buffer combinedEncClientToServer.limit( combinedEncClientToServer.limit() - (encClientToServerLen - positionOffset)); result = server.unwrap(combinedEncClientToServer, plainServerOut); assertEquals(encClientToServerLen, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testMultipleRecordsInOneBufferBiggerThenPacketBufferSize(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainClientOut = allocateBuffer(param.type(), 4096); ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); ByteBuffer encClientToServer = allocateBuffer(param.type(), server.getSession().getPacketBufferSize() * 2); handshake(param.type(), param.delegate(), client, server); int srcLen = plainClientOut.remaining(); SSLEngineResult result; int count = 0; do { int plainClientOutPosition = plainClientOut.position(); int encClientToServerPosition = encClientToServer.position(); result = client.wrap(plainClientOut, encClientToServer); if (result.getStatus() == Status.BUFFER_OVERFLOW) { // We did not have enough room to wrap assertEquals(plainClientOutPosition, plainClientOut.position()); assertEquals(encClientToServerPosition, encClientToServer.position()); break; } assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(srcLen, result.bytesConsumed()); assertTrue(result.bytesProduced() > 0); plainClientOut.clear(); ++count; } while (encClientToServer.position() < server.getSession().getPacketBufferSize()); // Check that we were able to wrap multiple times. assertTrue(count >= 2); encClientToServer.flip(); result = server.unwrap(encClientToServer, plainServerOut); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertTrue(result.bytesConsumed() > 0); assertTrue(result.bytesProduced() > 0); assertTrue(encClientToServer.hasRemaining()); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testBufferUnderFlow(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainClient = allocateBuffer(param.type(), 1024); plainClient.limit(plainClient.capacity()); ByteBuffer encClientToServer = allocateBuffer(param.type(), client.getSession().getPacketBufferSize()); ByteBuffer plainServer = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize()); handshake(param.type(), param.delegate(), client, server); SSLEngineResult result = client.wrap(plainClient, encClientToServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(result.bytesConsumed(), plainClient.capacity()); // Flip so we can read it. encClientToServer.flip(); int remaining = encClientToServer.remaining(); // We limit the buffer so we have less then the header to read, this should result in an BUFFER_UNDERFLOW. encClientToServer.limit(SSL_RECORD_HEADER_LENGTH - 1); result = server.unwrap(encClientToServer, plainServer); assertResultIsBufferUnderflow(result); // We limit the buffer so we can read the header but not the rest, this should result in an // BUFFER_UNDERFLOW. encClientToServer.limit(SSL_RECORD_HEADER_LENGTH); result = server.unwrap(encClientToServer, plainServer); assertResultIsBufferUnderflow(result); // We limit the buffer so we can read the header and partly the rest, this should result in an // BUFFER_UNDERFLOW. encClientToServer.limit(SSL_RECORD_HEADER_LENGTH + remaining - 1 - SSL_RECORD_HEADER_LENGTH); result = server.unwrap(encClientToServer, plainServer); assertResultIsBufferUnderflow(result); // Reset limit so we can read the full record. encClientToServer.limit(remaining); result = server.unwrap(encClientToServer, plainServer); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(result.bytesConsumed(), remaining); assertTrue(result.bytesProduced() > 0); } finally { cert.delete(); cleanupClientSslEngine(client); cleanupServerSslEngine(server); } } private static void assertResultIsBufferUnderflow(SSLEngineResult result) { assertEquals(SSLEngineResult.Status.BUFFER_UNDERFLOW, result.getStatus()); assertEquals(0, result.bytesConsumed()); assertEquals(0, result.bytesProduced()); } @MethodSource(""newTestParams"") @ParameterizedTest public void testWrapDoesNotZeroOutSrc(SSLEngineTestParam param) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(cert.cert()) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { ByteBuffer plainServerOut = allocateBuffer(param.type(), server.getSession().getApplicationBufferSize() / 2); handshake(param.type(), param.delegate(), client, server); // Fill the whole buffer and flip it. for (int i = 0; i < plainServerOut.capacity(); i++) { plainServerOut.put(i, (byte) i); } plainServerOut.position(plainServerOut.capacity()); plainServerOut.flip(); ByteBuffer encryptedServerToClient = allocateBuffer(param.type(), server.getSession().getPacketBufferSize()); SSLEngineResult result = server.wrap(plainServerOut, encryptedServerToClient); assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertTrue(result.bytesConsumed() > 0); for (int i = 0; i < plainServerOut.capacity(); i++) { assertEquals((byte) i, plainServerOut.get(i)); } } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testDisableProtocols(SSLEngineTestParam param) throws Exception { testDisableProtocols(param, SslProtocols.SSL_v2, SslProtocols.SSL_v2); testDisableProtocols(param, SslProtocols.SSL_v3, SslProtocols.SSL_v2, SslProtocols.SSL_v3); testDisableProtocols(param, SslProtocols.TLS_v1, SslProtocols.SSL_v2, SslProtocols.SSL_v3, SslProtocols.TLS_v1); testDisableProtocols(param, SslProtocols.TLS_v1_1, SslProtocols.SSL_v2, SslProtocols.SSL_v3, SslProtocols.TLS_v1, SslProtocols.TLS_v1_1); testDisableProtocols(param, SslProtocols.TLS_v1_2, SslProtocols.SSL_v2, SslProtocols.SSL_v3, SslProtocols.TLS_v1, SslProtocols.TLS_v1_1, SslProtocols.TLS_v1_2); } private void testDisableProtocols(SSLEngineTestParam param, String protocol, String... disabledProtocols) throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); SslContext ctx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine server = wrapEngine(ctx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { Set supported = new HashSet(Arrays.asList(server.getSupportedProtocols())); if (supported.contains(protocol)) { server.setEnabledProtocols(server.getSupportedProtocols()); assertEquals(supported, new HashSet(Arrays.asList(server.getSupportedProtocols()))); for (String disabled : disabledProtocols) { supported.remove(disabled); } if (supported.contains(SslProtocols.SSL_v2_HELLO) && supported.size() == 1) { // It's not allowed to set only PROTOCOL_SSL_V2_HELLO if using JDK SSLEngine. return; } server.setEnabledProtocols(supported.toArray(new String[0])); assertEquals(supported, new HashSet(Arrays.asList(server.getEnabledProtocols()))); server.setEnabledProtocols(server.getSupportedProtocols()); } } finally { cleanupServerSslEngine(server); cleanupClientSslContext(ctx); cert.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testUsingX509TrustManagerVerifiesHostname(SSLEngineTestParam param) throws Exception { testUsingX509TrustManagerVerifiesHostname(param, false); } @MethodSource(""newTestParams"") @ParameterizedTest public void testUsingX509TrustManagerVerifiesSNIHostname(SSLEngineTestParam param) throws Exception { testUsingX509TrustManagerVerifiesHostname(param, true); } private void testUsingX509TrustManagerVerifiesHostname(SSLEngineTestParam param, boolean useSNI) throws Exception { if (clientSslContextProvider() != null) { // Not supported when using conscrypt return; } String fqdn = ""something.netty.io""; SelfSignedCertificate cert = new SelfSignedCertificate(fqdn); clientSslCtx = wrapContext(param, SslContextBuilder .forClient() .trustManager(new TrustManagerFactory(new TrustManagerFactorySpi() { @Override protected void engineInit(KeyStore keyStore) { // NOOP } @Override protected TrustManager[] engineGetTrustManagers() { // Provide a custom trust manager, this manager trust all certificates return new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { // NOOP } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { // NOOP } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return EmptyArrays.EMPTY_X509_CERTIFICATES; } } }; } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { } }, null, TrustManagerFactory.getDefaultAlgorithm()) { }) .sslContextProvider(clientSslContextProvider()) .sslProvider(sslClientProvider()) .build()); SSLEngine client = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT, ""127.0.0.1"", 1234)); SSLParameters sslParameters = client.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(""HTTPS""); if (useSNI) { sslParameters.setServerNames(Collections.singletonList(new SNIHostName(fqdn))); } client.setSSLParameters(sslParameters); serverSslCtx = wrapContext(param, SslContextBuilder .forServer(cert.certificate(), cert.privateKey()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .build()); SSLEngine server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); try { handshake(param.type(), param.delegate(), client, server); if (!useSNI) { fail(); } } catch (SSLException exception) { if (useSNI) { throw exception; } // [MASK] as the hostname not matches. } finally { cleanupClientSslEngine(client); cleanupServerSslEngine(server); cert.delete(); } } @Test public void testInvalidCipher() throws Exception { SelfSignedCertificate cert = new SelfSignedCertificate(); List cipherList = new ArrayList(); Collections.addAll(cipherList, ((SSLSocketFactory) SSLSocketFactory.getDefault()).getDefaultCipherSuites()); cipherList.add(""InvalidCipher""); SSLEngine server = null; try { serverSslCtx = wrapContext(null, SslContextBuilder.forServer(cert.key(), cert.cert()) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .ciphers(cipherList).build()); server = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); fail(); } catch (IllegalArgumentException [MASK] ) { // [MASK] when invalid cipher is used. } catch (SSLException [MASK] ) { // [MASK] when invalid cipher is used. } finally { cert.delete(); cleanupServerSslEngine(server); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testGetCiphersuite(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); String clientCipher = clientEngine.getSession().getCipherSuite(); String serverCipher = serverEngine.getSession().getCipherSuite(); assertEquals(clientCipher, serverCipher); assertEquals(param.protocolCipherCombo.cipher, clientCipher); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionCache(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); try { doHandshakeVerifyReusedAndClose(param, ""a.netty.io"", 9999, false); doHandshakeVerifyReusedAndClose(param, ""a.netty.io"", 9999, true); doHandshakeVerifyReusedAndClose(param, ""b.netty.io"", 9999, false); invalidateSessionsAndAssert(serverSslCtx.sessionContext()); invalidateSessionsAndAssert(clientSslCtx.sessionContext()); } finally { ssc.delete(); } } protected void invalidateSessionsAndAssert(SSLSessionContext context) { Enumeration ids = context.getIds(); while (ids.hasMoreElements()) { byte[] id = ids.nextElement(); SSLSession session = context.getSession(id); if (session != null) { session.invalidate(); assertFalse(session.isValid()); assertNull(context.getSession(id)); } } } private static void assertSessionCache(SSLSessionContext sessionContext, int numSessions) { Enumeration ids = sessionContext.getIds(); int numIds = 0; while (ids.hasMoreElements()) { numIds++; byte[] id = ids.nextElement(); assertNotEquals(0, id.length); SSLSession session = sessionContext.getSession(id); assertArrayEquals(id, session.getId()); } assertEquals(numSessions, numIds); } private void doHandshakeVerifyReusedAndClose(SSLEngineTestParam param, String host, int port, boolean reuse) throws Exception { SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT, host, port)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); int clientSessions = currentSessionCacheSize(clientSslCtx.sessionContext()); int serverSessions = currentSessionCacheSize(serverSslCtx.sessionContext()); int nCSessions = clientSessions; int nSSessions = serverSessions; boolean clientSessionReused = false; boolean serverSessionReused = false; if (param.protocolCipherCombo == ProtocolCipherCombo.TLSV13) { // Allocate something which is big enough for sure ByteBuffer packetBuffer = allocateBuffer(param.type(), 32 * 1024); ByteBuffer appBuffer = allocateBuffer(param.type(), 32 * 1024); appBuffer.clear().position(4).flip(); packetBuffer.clear(); do { SSLEngineResult result; do { result = serverEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = clientEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); do { result = clientEngine.wrap(appBuffer, packetBuffer); } while (appBuffer.hasRemaining() || result.bytesProduced() > 0); appBuffer.clear(); packetBuffer.flip(); do { result = serverEngine.unwrap(packetBuffer, appBuffer); } while (packetBuffer.hasRemaining() || result.bytesProduced() > 0); packetBuffer.clear(); appBuffer.clear().position(4).flip(); nCSessions = currentSessionCacheSize(clientSslCtx.sessionContext()); nSSessions = currentSessionCacheSize(serverSslCtx.sessionContext()); clientSessionReused = isSessionMaybeReused(clientEngine); serverSessionReused = isSessionMaybeReused(serverEngine); } while ((reuse && (!clientSessionReused || !serverSessionReused)) || (!reuse && (nCSessions < clientSessions || // server may use multiple sessions nSSessions < serverSessions))); } assertSessionReusedForEngine(clientEngine, serverEngine, reuse); closeOutboundAndInbound(param.type(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); } } protected boolean isSessionMaybeReused(SSLEngine engine) { return true; } private static int currentSessionCacheSize(SSLSessionContext ctx) { Enumeration ids = ctx.getIds(); int i = 0; while (ids.hasMoreElements()) { i++; ids.nextElement(); } return i; } private void closeOutboundAndInbound( BufferType type, SSLEngine clientEngine, SSLEngine serverEngine) throws SSLException { assertFalse(clientEngine.isInboundDone()); assertFalse(clientEngine.isOutboundDone()); assertFalse(serverEngine.isInboundDone()); assertFalse(serverEngine.isOutboundDone()); ByteBuffer empty = allocateBuffer(type, 0); // Ensure we allocate a bit more so we can fit in multiple packets. This is needed as we may call multiple // time wrap / unwrap in a for loop before we drain the buffer we are writing in. ByteBuffer cTOs = allocateBuffer(type, clientEngine.getSession().getPacketBufferSize() * 4); ByteBuffer sTOs = allocateBuffer(type, serverEngine.getSession().getPacketBufferSize() * 4); ByteBuffer cApps = allocateBuffer(type, clientEngine.getSession().getApplicationBufferSize() * 4); ByteBuffer sApps = allocateBuffer(type, serverEngine.getSession().getApplicationBufferSize() * 4); clientEngine.closeOutbound(); for (;;) { // call wrap till we produced all data SSLEngineResult result = clientEngine.wrap(empty, cTOs); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(cTOs.hasRemaining()); } cTOs.flip(); for (;;) { // call unwrap till we consumed all data SSLEngineResult result = serverEngine.unwrap(cTOs, sApps); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(sApps.hasRemaining()); } serverEngine.closeOutbound(); for (;;) { // call wrap till we produced all data SSLEngineResult result = serverEngine.wrap(empty, sTOs); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(sTOs.hasRemaining()); } sTOs.flip(); for (;;) { // call unwrap till we consumed all data SSLEngineResult result = clientEngine.unwrap(sTOs, cApps); if (result.getStatus() == Status.CLOSED && result.bytesProduced() == 0) { break; } assertTrue(cApps.hasRemaining()); } // Now close the inbound as well clientEngine.closeInbound(); serverEngine.closeInbound(); } protected void assertSessionReusedForEngine(SSLEngine clientEngine, SSLEngine serverEngine, boolean reuse) { // NOOP } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionCacheTimeout(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sessionTimeout(1) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sessionTimeout(1) .build()); try { doHandshakeVerifyReusedAndClose(param, ""a.netty.io"", 9999, false); // Let's sleep for a bit more then 1 second so the cache should timeout the sessions. Thread.sleep(1500); assertSessionCache(serverSslCtx.sessionContext(), 0); assertSessionCache(clientSslCtx.sessionContext(), 0); } finally { ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionCacheSize(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .sessionCacheSize(1) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); try { doHandshakeVerifyReusedAndClose(param, ""a.netty.io"", 9999, false); // As we have a cache size of 1 we should never have more then one session in the cache doHandshakeVerifyReusedAndClose(param, ""b.netty.io"", 9999, false); // We should at least reuse b.netty.io doHandshakeVerifyReusedAndClose(param, ""b.netty.io"", 9999, true); } finally { ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionBindingEvent(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession session = clientEngine.getSession(); assertEquals(0, session.getValueNames().length); class SSLSessionBindingEventValue implements SSLSessionBindingListener { SSLSessionBindingEvent boundEvent; SSLSessionBindingEvent unboundEvent; @Override public void valueBound(SSLSessionBindingEvent sslSessionBindingEvent) { assertNull(boundEvent); boundEvent = sslSessionBindingEvent; } @Override public void valueUnbound(SSLSessionBindingEvent sslSessionBindingEvent) { assertNull(unboundEvent); unboundEvent = sslSessionBindingEvent; } } String name = ""name""; String name2 = ""name2""; SSLSessionBindingEventValue value1 = new SSLSessionBindingEventValue(); session.putValue(name, value1); assertSSLSessionBindingEventValue(name, session, value1.boundEvent); assertNull(value1.unboundEvent); assertEquals(1, session.getValueNames().length); session.putValue(name2, ""value""); SSLSessionBindingEventValue value2 = new SSLSessionBindingEventValue(); session.putValue(name, value2); assertEquals(2, session.getValueNames().length); assertSSLSessionBindingEventValue(name, session, value1.unboundEvent); assertSSLSessionBindingEventValue(name, session, value2.boundEvent); assertNull(value2.unboundEvent); assertEquals(2, session.getValueNames().length); session.removeValue(name); assertSSLSessionBindingEventValue(name, session, value2.unboundEvent); assertEquals(1, session.getValueNames().length); session.removeValue(name2); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } private static void assertSSLSessionBindingEventValue( String name, SSLSession session, SSLSessionBindingEvent event) { assertEquals(name, event.getName()); assertEquals(session, event.getSession()); assertEquals(session, event.getSource()); } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionAfterHandshake(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, false, false); } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionAfterHandshakeMutualAuth(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, false, true); } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionAfterHandshakeKeyManagerFactory(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, true, false); } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionAfterHandshakeKeyManagerFactoryMutualAuth(SSLEngineTestParam param) throws Exception { testSessionAfterHandshake0(param, true, true); } private void testSessionAfterHandshake0( SSLEngineTestParam param, boolean useKeyManagerFactory, boolean mutualAuth) throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); KeyManagerFactory kmf = useKeyManagerFactory ? SslContext.buildKeyManagerFactory( new java.security.cert.X509Certificate[] { ssc.cert()}, null, ssc.key(), null, null, null) : null; SslContextBuilder clientContextBuilder = SslContextBuilder.forClient(); if (mutualAuth) { if (kmf != null) { clientContextBuilder.keyManager(kmf); } else { clientContextBuilder.keyManager(ssc.key(), ssc.cert()); } } clientSslCtx = wrapContext(param, clientContextBuilder .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SslContextBuilder serverContextBuilder = kmf != null ? SslContextBuilder.forServer(kmf) : SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()); if (mutualAuth) { serverContextBuilder.clientAuth(ClientAuth.REQUIRE); } serverSslCtx = wrapContext(param, serverContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession clientSession = clientEngine.getSession(); SSLSession serverSession = serverEngine.getSession(); assertNull(clientSession.getPeerHost()); assertNull(serverSession.getPeerHost()); assertEquals(-1, clientSession.getPeerPort()); assertEquals(-1, serverSession.getPeerPort()); assertTrue(clientSession.getCreationTime() > 0); assertTrue(serverSession.getCreationTime() > 0); assertTrue(clientSession.getLastAccessedTime() > 0); assertTrue(serverSession.getLastAccessedTime() > 0); assertEquals(param.combo().protocol, clientSession.getProtocol()); assertEquals(param.combo().protocol, serverSession.getProtocol()); assertEquals(param.combo().cipher, clientSession.getCipherSuite()); assertEquals(param.combo().cipher, serverSession.getCipherSuite()); assertNotNull(clientSession.getId()); assertNotNull(serverSession.getId()); assertTrue(clientSession.getApplicationBufferSize() > 0); assertTrue(serverSession.getApplicationBufferSize() > 0); assertTrue(clientSession.getPacketBufferSize() > 0); assertTrue(serverSession.getPacketBufferSize() > 0); assertNotNull(clientSession.getSessionContext()); // Workaround for JDK 14 regression. // See https://bugs.openjdk.java.net/browse/JDK-8242008 if (PlatformDependent.javaVersion() != 14) { assertNotNull(serverSession.getSessionContext()); } Object value = new Object(); assertEquals(0, clientSession.getValueNames().length); clientSession.putValue(""test"", value); assertEquals(""test"", clientSession.getValueNames()[0]); assertSame(value, clientSession.getValue(""test"")); clientSession.removeValue(""test""); assertEquals(0, clientSession.getValueNames().length); assertEquals(0, serverSession.getValueNames().length); serverSession.putValue(""test"", value); assertEquals(""test"", serverSession.getValueNames()[0]); assertSame(value, serverSession.getValue(""test"")); serverSession.removeValue(""test""); assertEquals(0, serverSession.getValueNames().length); Certificate[] serverLocalCertificates = serverSession.getLocalCertificates(); assertEquals(1, serverLocalCertificates.length); assertArrayEquals(ssc.cert().getEncoded(), serverLocalCertificates[0].getEncoded()); Principal serverLocalPrincipal = serverSession.getLocalPrincipal(); assertNotNull(serverLocalPrincipal); if (mutualAuth) { Certificate[] clientLocalCertificates = clientSession.getLocalCertificates(); assertEquals(1, clientLocalCertificates.length); Certificate[] serverPeerCertificates = serverSession.getPeerCertificates(); assertEquals(1, serverPeerCertificates.length); assertArrayEquals(clientLocalCertificates[0].getEncoded(), serverPeerCertificates[0].getEncoded()); try { X509Certificate[] serverPeerX509Certificates = serverSession.getPeerCertificateChain(); assertEquals(1, serverPeerX509Certificates.length); assertArrayEquals(clientLocalCertificates[0].getEncoded(), serverPeerX509Certificates[0].getEncoded()); } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } Principal clientLocalPrincipial = clientSession.getLocalPrincipal(); assertNotNull(clientLocalPrincipial); Principal serverPeerPrincipal = serverSession.getPeerPrincipal(); assertEquals(clientLocalPrincipial, serverPeerPrincipal); } else { assertNull(clientSession.getLocalCertificates()); assertNull(clientSession.getLocalPrincipal()); try { serverSession.getPeerCertificates(); fail(); } catch (SSLPeerUnverifiedException [MASK] ) { // As we did not use mutual auth this is [MASK] } try { serverSession.getPeerCertificateChain(); fail(); } catch (SSLPeerUnverifiedException [MASK] ) { // As we did not use mutual auth this is [MASK] } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } try { serverSession.getPeerPrincipal(); fail(); } catch (SSLPeerUnverifiedException [MASK] ) { // As we did not use mutual auth this is [MASK] } } Certificate[] clientPeerCertificates = clientSession.getPeerCertificates(); assertEquals(1, clientPeerCertificates.length); assertArrayEquals(serverLocalCertificates[0].getEncoded(), clientPeerCertificates[0].getEncoded()); try { X509Certificate[] clientPeerX509Certificates = clientSession.getPeerCertificateChain(); assertEquals(1, clientPeerX509Certificates.length); assertArrayEquals(serverLocalCertificates[0].getEncoded(), clientPeerX509Certificates[0].getEncoded()); } catch (UnsupportedOperationException e) { // See https://bugs.openjdk.java.net/browse/JDK-8241039 assertTrue(PlatformDependent.javaVersion() >= 15); } Principal clientPeerPrincipal = clientSession.getPeerPrincipal(); assertEquals(serverLocalPrincipal, clientPeerPrincipal); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSupportedSignatureAlgorithms(SSLEngineTestParam param) throws Exception { final SelfSignedCertificate ssc = new SelfSignedCertificate(); final class TestKeyManagerFactory extends KeyManagerFactory { TestKeyManagerFactory(final KeyManagerFactory factory) { super(new KeyManagerFactorySpi() { private final KeyManager[] managers = factory.getKeyManagers(); @Override protected void engineInit(KeyStore keyStore, char[] chars) { throw new UnsupportedOperationException(); } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { throw new UnsupportedOperationException(); } @Override protected KeyManager[] engineGetKeyManagers() { KeyManager[] array = new KeyManager[managers.length]; for (int i = 0 ; i < array.length; i++) { final X509ExtendedKeyManager x509ExtendedKeyManager = (X509ExtendedKeyManager) managers[i]; array[i] = new X509ExtendedKeyManager() { @Override public String[] getClientAliases(String s, Principal[] principals) { fail(); return null; } @Override public String chooseClientAlias( String[] strings, Principal[] principals, Socket socket) { fail(); return null; } @Override public String[] getServerAliases(String s, Principal[] principals) { fail(); return null; } @Override public String chooseServerAlias(String s, Principal[] principals, Socket socket) { fail(); return null; } @Override public String chooseEngineClientAlias( String[] strings, Principal[] principals, SSLEngine sslEngine) { assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getPeerSupportedSignatureAlgorithms().length); assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getLocalSupportedSignatureAlgorithms().length); return x509ExtendedKeyManager.chooseEngineClientAlias( strings, principals, sslEngine); } @Override public String chooseEngineServerAlias( String s, Principal[] principals, SSLEngine sslEngine) { assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getPeerSupportedSignatureAlgorithms().length); assertNotEquals(0, ((ExtendedSSLSession) sslEngine.getHandshakeSession()) .getLocalSupportedSignatureAlgorithms().length); return x509ExtendedKeyManager.chooseEngineServerAlias(s, principals, sslEngine); } @Override public java.security.cert.X509Certificate[] getCertificateChain(String s) { return x509ExtendedKeyManager.getCertificateChain(s); } @Override public PrivateKey getPrivateKey(String s) { return x509ExtendedKeyManager.getPrivateKey(s); } }; } return array; } }, factory.getProvider(), factory.getAlgorithm()); } } clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .keyManager(new TestKeyManagerFactory(newKeyManagerFactory(ssc))) .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer( new TestKeyManagerFactory(newKeyManagerFactory(ssc))) .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslContextProvider(serverSslContextProvider()) .sslProvider(sslServerProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.REQUIRE) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testHandshakeSession(SSLEngineTestParam param) throws Exception { final SelfSignedCertificate ssc = new SelfSignedCertificate(); final TestTrustManagerFactory clientTmf = new TestTrustManagerFactory(ssc.cert()); final TestTrustManagerFactory serverTmf = new TestTrustManagerFactory(ssc.cert()); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(new SimpleTrustManagerFactory() { @Override protected void engineInit(KeyStore keyStore) { // NOOP } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { // NOOP } @Override protected TrustManager[] engineGetTrustManagers() { return new TrustManager[] { clientTmf }; } }) .keyManager(newKeyManagerFactory(ssc)) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(newKeyManagerFactory(ssc)) .trustManager(new SimpleTrustManagerFactory() { @Override protected void engineInit(KeyStore keyStore) { // NOOP } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { // NOOP } @Override protected TrustManager[] engineGetTrustManagers() { return new TrustManager[] { serverTmf }; } }) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.REQUIRE) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); assertTrue(clientTmf.isVerified()); assertTrue(serverTmf.isVerified()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionLocalWhenNonMutualWithKeyManager(SSLEngineTestParam param) throws Exception { testSessionLocalWhenNonMutual(param, true); } @MethodSource(""newTestParams"") @ParameterizedTest public void testSessionLocalWhenNonMutualWithoutKeyManager(SSLEngineTestParam param) throws Exception { testSessionLocalWhenNonMutual(param, false); } private void testSessionLocalWhenNonMutual(SSLEngineTestParam param, boolean useKeyManager) throws Exception { final SelfSignedCertificate ssc = new SelfSignedCertificate(); SslContextBuilder clientSslCtxBuilder = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()); if (useKeyManager) { clientSslCtxBuilder.keyManager(newKeyManagerFactory(ssc)); } else { clientSslCtxBuilder.keyManager(ssc.certificate(), ssc.privateKey()); } clientSslCtx = wrapContext(param, clientSslCtxBuilder.build()); final SslContextBuilder serverSslCtxBuilder; if (useKeyManager) { serverSslCtxBuilder = SslContextBuilder.forServer(newKeyManagerFactory(ssc)); } else { serverSslCtxBuilder = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()); } serverSslCtxBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.NONE); serverSslCtx = wrapContext(param, serverSslCtxBuilder.build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); SSLSession clientSession = clientEngine.getSession(); assertNull(clientSession.getLocalCertificates()); assertNull(clientSession.getLocalPrincipal()); SSLSession serverSession = serverEngine.getSession(); assertNotNull(serverSession.getLocalCertificates()); assertNotNull(serverSession.getLocalPrincipal()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testEnabledProtocolsAndCiphers(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); assertEnabledProtocolsAndCipherSuites(clientEngine); assertEnabledProtocolsAndCipherSuites(serverEngine); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } } private static void assertEnabledProtocolsAndCipherSuites(SSLEngine engine) { String protocol = engine.getSession().getProtocol(); String cipherSuite = engine.getSession().getCipherSuite(); assertArrayContains(protocol, engine.getEnabledProtocols()); assertArrayContains(cipherSuite, engine.getEnabledCipherSuites()); } private static void assertArrayContains(String [MASK] , String[] array) { for (String value: array) { if ( [MASK] .equals(value)) { return; } } fail(""Array did not contain '"" + [MASK] + ""':"" + Arrays.toString(array)); } @MethodSource(""newTestParams"") @ParameterizedTest public void testMasterKeyLogging(final SSLEngineTestParam param) throws Exception { if (param.combo() != ProtocolCipherCombo.tlsv12()) { return; } /* * At the moment master key logging is not supported for conscrypt */ assumeFalse(serverSslContextProvider() instanceof OpenSSLProvider); /* * The JDK SSL engine master key retrieval relies on being able to set field access to true. * That is not available in JDK9+ */ assumeFalse(sslServerProvider() == SslProvider.JDK && PlatformDependent.javaVersion() > 8); String originalSystemPropertyValue = SystemPropertyUtil.get(SslMasterKeyHandler.SYSTEM_PROP_KEY); System.setProperty(SslMasterKeyHandler.SYSTEM_PROP_KEY, Boolean.TRUE.toString()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); Socket socket = null; try { sb = new ServerBootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); final Promise promise = sb.config().group().next().newPromise(); serverChannel = sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.config().setAllocator(new TestByteBufAllocator(ch.config().getAllocator(), param.type())); SslHandler sslHandler = !param.delegate() ? serverSslCtx.newHandler(ch.alloc()) : serverSslCtx.newHandler(ch.alloc(), delegatingExecutor); ch.pipeline().addLast(sslHandler); ch.pipeline().addLast(new SslMasterKeyHandler() { @Override protected void accept(SecretKey masterKey, SSLSession session) { promise.setSuccess(masterKey); } }); serverConnectedChannel = ch; } }).bind(new InetSocketAddress(0)).sync().channel(); int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); SSLContext sslContext = SSLContext.getInstance(""TLS""); sslContext.init(null, InsecureTrustManagerFactory.INSTANCE.getTrustManagers(), null); socket = sslContext.getSocketFactory().createSocket(NetUtil.LOCALHOST, port); OutputStream out = socket.getOutputStream(); out.write(1); out.flush(); assertTrue(promise.await(10, TimeUnit.SECONDS)); SecretKey key = promise.get(); assertEquals(48, key.getEncoded().length, ""AES secret key must be 48 bytes""); } finally { closeQuietly(socket); if (originalSystemPropertyValue != null) { System.setProperty(SslMasterKeyHandler.SYSTEM_PROP_KEY, originalSystemPropertyValue); } else { System.clearProperty(SslMasterKeyHandler.SYSTEM_PROP_KEY); } ssc.delete(); } } private static void closeQuietly(Closeable c) { if (c != null) { try { c.close(); } catch (IOException ignore) { // ignore } } } private static KeyManagerFactory newKeyManagerFactory(SelfSignedCertificate ssc) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { return SslContext.buildKeyManagerFactory( new java.security.cert.X509Certificate[] { ssc.cert() }, null, ssc.key(), null, null, null); } private static final class TestTrustManagerFactory extends X509ExtendedTrustManager { private final Certificate localCert; private volatile boolean verified; TestTrustManagerFactory(Certificate localCert) { this.localCert = localCert; } boolean isVerified() { return verified; } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, Socket socket) { fail(); } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, Socket socket) { fail(); } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { verified = true; assertFalse(sslEngine.getUseClientMode()); SSLSession session = sslEngine.getHandshakeSession(); assertNotNull(session); Certificate[] localCertificates = session.getLocalCertificates(); assertNotNull(localCertificates); assertEquals(1, localCertificates.length); assertEquals(localCert, localCertificates[0]); assertNotNull(session.getLocalPrincipal()); } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { verified = true; assertTrue(sslEngine.getUseClientMode()); SSLSession session = sslEngine.getHandshakeSession(); assertNotNull(session); assertNull(session.getLocalCertificates()); assertNull(session.getLocalPrincipal()); } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { fail(); } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) { fail(); } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return EmptyArrays.EMPTY_X509_CERTIFICATES; } } @MethodSource(""newTestParams"") @ParameterizedTest public void testDefaultProtocolsIncludeTLSv13(SSLEngineTestParam param) throws Exception { // Don't specify the protocols as we want to test the default selection clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; String[] clientProtocols; String[] serverProtocols; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); clientProtocols = clientEngine.getEnabledProtocols(); serverProtocols = serverEngine.getEnabledProtocols(); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); ssc.delete(); } assertEquals(SslProvider.isTlsv13EnabledByDefault(sslClientProvider(), clientSslContextProvider()), arrayContains(clientProtocols, SslProtocols.TLS_v1_3)); assertEquals(SslProvider.isTlsv13EnabledByDefault(sslServerProvider(), serverSslContextProvider()), arrayContains(serverProtocols, SslProtocols.TLS_v1_3)); } // IMPORTANT: If this test fails, try rerunning the 'generate-certificate.sh' script. @MethodSource(""newTestParams"") @ParameterizedTest public void testRSASSAPSS(SSLEngineTestParam param) throws Exception { char[] password = ""password"".toCharArray(); final KeyStore serverKeyStore = KeyStore.getInstance(""PKCS12""); serverKeyStore.load(getClass().getResourceAsStream(""rsaValidations-server-keystore.p12""), password); final KeyStore clientKeyStore = KeyStore.getInstance(""PKCS12""); clientKeyStore.load(getClass().getResourceAsStream(""rsaValidation-user-certs.p12""), password); final KeyManagerFactory serverKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); serverKeyManagerFactory.init(serverKeyStore, password); final KeyManagerFactory clientKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); clientKeyManagerFactory.init(clientKeyStore, password); File commonChain = ResourcesUtil.getFile(getClass(), ""rsapss-ca-cert.cert""); ClientAuth auth = ClientAuth.REQUIRE; mySetupMutualAuth(param, serverKeyManagerFactory, commonChain, clientKeyManagerFactory, commonChain, auth, false, true); assertTrue(clientLatch.await(10, TimeUnit.SECONDS)); rethrowIfNotNull(clientException); assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); rethrowIfNotNull(serverException); } @MethodSource(""newTestParams"") @ParameterizedTest public void testInvalidSNIIsIgnoredAndNotThrow(SSLEngineTestParam param) throws Exception { clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SelfSignedCertificate ssc = new SelfSignedCertificate(); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT, ""/invalid.path"", 80)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); assertNotNull(clientEngine.getSSLParameters()); assertNotNull(serverEngine.getSSLParameters()); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); } } @MethodSource(""newTestParams"") @ParameterizedTest public void testBufferUnderflowPacketSizeDependency(SSLEngineTestParam param) throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .keyManager(ssc.certificate(), ssc.privateKey()) .trustManager((TrustManagerFactory) null) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.REQUIRE) .build()); SSLEngine clientEngine = null; SSLEngine serverEngine = null; try { clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)); handshake(param.type(), param.delegate(), clientEngine, serverEngine); } catch (SSLHandshakeException [MASK] ) { // Expected } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); } } @Test public void testExtraDataInLastSrcBufferForClientUnwrap() throws Exception { SSLEngineTestParam param = new SSLEngineTestParam(BufferType.Direct, ProtocolCipherCombo.tlsv12(), false); SelfSignedCertificate ssc = new SelfSignedCertificate(); clientSslCtx = wrapContext(param, SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(sslClientProvider()) .sslContextProvider(clientSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .build()); serverSslCtx = wrapContext(param, SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) .sslProvider(sslServerProvider()) .sslContextProvider(serverSslContextProvider()) .protocols(param.protocols()) .ciphers(param.ciphers()) .clientAuth(ClientAuth.NONE) .build()); testExtraDataInLastSrcBufferForClientUnwrap(param, wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT)), wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT))); } protected final void testExtraDataInLastSrcBufferForClientUnwrap( SSLEngineTestParam param, SSLEngine clientEngine, SSLEngine serverEngine) throws Exception { try { ByteBuffer cTOs = allocateBuffer(param.type(), clientEngine.getSession().getPacketBufferSize()); // Ensure we can fit two records as we want to include two records once the handshake completes on the // server side. ByteBuffer sTOc = allocateBuffer(param.type(), serverEngine.getSession().getPacketBufferSize() * 2); ByteBuffer serverAppReadBuffer = allocateBuffer(param.type(), serverEngine.getSession().getApplicationBufferSize()); ByteBuffer clientAppReadBuffer = allocateBuffer(param.type(), clientEngine.getSession().getApplicationBufferSize()); ByteBuffer empty = allocateBuffer(param.type(), 0); SSLEngineResult clientResult; SSLEngineResult serverResult; boolean clientHandshakeFinished = false; boolean serverHandshakeFinished = false; do { int cTOsPos = cTOs.position(); int sTOcPos = sTOc.position(); if (!clientHandshakeFinished) { clientResult = clientEngine.wrap(empty, cTOs); runDelegatedTasks(param.delegate(), clientResult, clientEngine); assertEquals(empty.remaining(), clientResult.bytesConsumed()); assertEquals(cTOs.position() - cTOsPos, clientResult.bytesProduced()); if (isHandshakeFinished(clientResult)) { clientHandshakeFinished = true; } if (clientResult.getStatus() == Status.BUFFER_OVERFLOW) { cTOs = increaseDstBuffer(clientEngine.getSession().getPacketBufferSize(), param.type(), cTOs); } } if (!serverHandshakeFinished) { serverResult = serverEngine.wrap(empty, sTOc); runDelegatedTasks(param.delegate(), serverResult, serverEngine); assertEquals(empty.remaining(), serverResult.bytesConsumed()); assertEquals(sTOc.position() - sTOcPos, serverResult.bytesProduced()); if (isHandshakeFinished(serverResult)) { serverHandshakeFinished = true; // We finished the handshake on the server side, lets add another record to the sTOc buffer // so we can test that we will not unwrap extra data before we actually consider the handshake // complete on the client side as well. serverResult = serverEngine.wrap(ByteBuffer.wrap(new byte[8]), sTOc); assertEquals(8, serverResult.bytesConsumed()); } if (serverResult.getStatus() == Status.BUFFER_OVERFLOW) { sTOc = increaseDstBuffer(serverEngine.getSession().getPacketBufferSize(), param.type(), sTOc); } } cTOs.flip(); sTOc.flip(); cTOsPos = cTOs.position(); sTOcPos = sTOc.position(); if (!clientHandshakeFinished) { int clientAppReadBufferPos = clientAppReadBuffer.position(); clientResult = clientEngine.unwrap(sTOc, clientAppReadBuffer); runDelegatedTasks(param.delegate(), clientResult, clientEngine); assertEquals(sTOc.position() - sTOcPos, clientResult.bytesConsumed()); assertEquals(clientAppReadBuffer.position() - clientAppReadBufferPos, clientResult.bytesProduced()); assertEquals(0, clientAppReadBuffer.position()); if (isHandshakeFinished(clientResult)) { clientHandshakeFinished = true; } else { assertEquals(0, clientAppReadBuffer.position() - clientAppReadBufferPos); } if (clientResult.getStatus() == Status.BUFFER_OVERFLOW) { clientAppReadBuffer = increaseDstBuffer( clientEngine.getSession().getApplicationBufferSize(), param.type(), clientAppReadBuffer); } } if (!serverHandshakeFinished) { int serverAppReadBufferPos = serverAppReadBuffer.position(); serverResult = serverEngine.unwrap(cTOs, serverAppReadBuffer); runDelegatedTasks(param.delegate(), serverResult, serverEngine); assertEquals(cTOs.position() - cTOsPos, serverResult.bytesConsumed()); assertEquals(serverAppReadBuffer.position() - serverAppReadBufferPos, serverResult.bytesProduced()); assertEquals(0, serverAppReadBuffer.position()); if (isHandshakeFinished(serverResult)) { serverHandshakeFinished = true; } if (serverResult.getStatus() == Status.BUFFER_OVERFLOW) { serverAppReadBuffer = increaseDstBuffer( serverEngine.getSession().getApplicationBufferSize(), param.type(), serverAppReadBuffer); } } compactOrClear(cTOs); compactOrClear(sTOc); serverAppReadBuffer.clear(); clientAppReadBuffer.clear(); if (clientEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { clientHandshakeFinished = true; } if (serverEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { serverHandshakeFinished = true; } } while (!clientHandshakeFinished || !serverHandshakeFinished); } finally { cleanupClientSslEngine(clientEngine); cleanupServerSslEngine(serverEngine); } } protected SSLEngine wrapEngine(SSLEngine engine) { return engine; } protected SslContext wrapContext(SSLEngineTestParam param, SslContext context) { return context; } } ","expected " "/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.traffic; import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkNotNullWithIAE; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Counts the number of read and written bytes for rate-limiting traffic. *

* It computes the statistics for both inbound and outbound traffic periodically at the given * {@code checkInterval}, and calls the {@link AbstractTrafficShapingHandler#doAccounting(TrafficCounter)} method back. * If the {@code checkInterval} is {@code 0}, no accounting will be done and statistics will only be computed at each * receive or write operation. *

*/ public class TrafficCounter { private static final InternalLogger logger = InternalLoggerFactory.getInstance(TrafficCounter.class); /** * @return the time in ms using nanoTime, so not real EPOCH time but elapsed time in ms. */ public static long milliSecondFromNano() { return System.nanoTime() / 1000000; } /** * Current written bytes */ private final AtomicLong currentWrittenBytes = new AtomicLong(); /** * Current read bytes */ private final AtomicLong currentReadBytes = new AtomicLong(); /** * Last writing time during current check interval */ private long writingTime; /** * Last reading delay during current check interval */ private long readingTime; /** * Long life written bytes */ private final AtomicLong cumulativeWrittenBytes = new AtomicLong(); /** * Long life read bytes */ private final AtomicLong cumulativeReadBytes = new AtomicLong(); /** * Last Time where cumulative bytes where reset to zero: this time is a real EPOC time (informative only) */ private long lastCumulativeTime; /** * Last writing bandwidth */ private long lastWriteThroughput; /** * Last reading bandwidth */ private long lastReadThroughput; /** * Last Time Check taken */ final AtomicLong lastTime = new AtomicLong(); /** * Last written bytes number during last check interval */ private volatile long lastWrittenBytes; /** * Last read bytes number during last check interval */ private volatile long lastReadBytes; /** * Last future writing time during last check interval */ private volatile long lastWritingTime; /** * Last reading time during last check interval */ private volatile long lastReadingTime; /** * Real written bytes */ private final AtomicLong realWrittenBytes = new AtomicLong(); /** * Real writing bandwidth */ private long realWriteThroughput; /** * Delay between two captures */ final AtomicLong checkInterval = new AtomicLong( AbstractTrafficShapingHandler.DEFAULT_CHECK_INTERVAL); // default 1 s /** * Name of this Monitor */ final String name; /** * The associated TrafficShapingHandler */ final AbstractTrafficShapingHandler trafficShapingHandler; /** * Executor that will run the monitor */ final ScheduledExecutorService executor; /** * Monitor created once in start() */ Runnable monitor; /** * used in stop() to cancel the timer */ volatile ScheduledFuture scheduledFuture; /** * Is Monitor active */ volatile boolean monitorActive; /** * Class to implement monitoring at fix delay * */ private final class TrafficMonitoringTask implements Runnable { @Override public void run() { if (!monitorActive) { return; } resetAccounting(milliSecondFromNano()); if (trafficShapingHandler != null) { trafficShapingHandler.doAccounting(TrafficCounter.this); } } } /** * Start the monitoring process. */ public synchronized void start() { if (monitorActive) { return; } lastTime.set(milliSecondFromNano()); long localCheckInterval = checkInterval.get(); // if executor is null, it means it is piloted by a GlobalChannelTrafficCounter, so no executor if (localCheckInterval > 0 && executor != null) { monitorActive = true; monitor = new TrafficMonitoringTask(); scheduledFuture = executor.scheduleAtFixedRate(monitor, 0, localCheckInterval, TimeUnit.MILLISECONDS); } } /** * Stop the monitoring process. */ public synchronized void stop() { if (!monitorActive) { return; } monitorActive = false; resetAccounting(milliSecondFromNano()); if (trafficShapingHandler != null) { trafficShapingHandler.doAccounting(this); } if (scheduledFuture != null) { scheduledFuture.cancel(true); } } /** * Reset the accounting on Read and Write. * * @param newLastTime the milliseconds unix timestamp that we should be considered up-to-date for. */ synchronized void resetAccounting(long newLastTime) { long interval = newLastTime - lastTime.getAndSet(newLastTime); if (interval == 0) { // nothing to do return; } if (logger.isDebugEnabled() && interval > checkInterval() << 1) { logger.debug(""Acct schedule not ok: "" + interval + "" > 2*"" + checkInterval() + "" from "" + name); } lastReadBytes = currentReadBytes.getAndSet(0); lastWrittenBytes = currentWrittenBytes.getAndSet(0); lastReadThroughput = lastReadBytes * 1000 / interval; // nb byte / checkInterval in ms * 1000 (1s) lastWriteThroughput = lastWrittenBytes * 1000 / interval; // nb byte / checkInterval in ms * 1000 (1s) realWriteThroughput = realWrittenBytes.getAndSet(0) * 1000 / interval; lastWritingTime = Math.max(lastWritingTime, writingTime); lastReadingTime = Math.max(lastReadingTime, readingTime); } /** * Constructor with the {@link AbstractTrafficShapingHandler} that hosts it, the {@link ScheduledExecutorService} * to use, its name, the checkInterval between two computations in milliseconds. * * @param executor * the underlying executor service for scheduling checks, might be null when used * from {@link GlobalChannelTrafficCounter}. * @param name * the name given to this monitor. * @param checkInterval * the checkInterval in millisecond between two computations. */ public TrafficCounter(ScheduledExecutorService executor, String name, long checkInterval) { this.name = checkNotNull(name, ""name""); trafficShapingHandler = null; this.executor = executor; init(checkInterval); } /** * Constructor with the {@link AbstractTrafficShapingHandler} that hosts it, the Timer to use, its * name, the checkInterval between two computations in millisecond. * * @param trafficShapingHandler * the associated AbstractTrafficShapingHandler. * @param executor * the underlying executor service for scheduling checks, might be null when used * from {@link GlobalChannelTrafficCounter}. * @param name * the name given to this monitor. * @param checkInterval * the checkInterval in millisecond between two computations. */ public TrafficCounter( AbstractTrafficShapingHandler trafficShapingHandler, ScheduledExecutorService executor, String name, long checkInterval) { this.name = checkNotNull(name, ""name""); this.trafficShapingHandler = checkNotNullWithIAE(trafficShapingHandler, ""trafficShapingHandler""); this.executor = executor; init(checkInterval); } private void init(long checkInterval) { // absolute time: informative only lastCumulativeTime = System.currentTimeMillis(); writingTime = milliSecondFromNano(); readingTime = writingTime; lastWritingTime = writingTime; lastReadingTime = writingTime; configure(checkInterval); } /** * Change checkInterval between two computations in millisecond. * * @param newCheckInterval The new check interval (in milliseconds) */ public void configure(long newCheckInterval) { long [MASK] = newCheckInterval / 10 * 10; if (checkInterval.getAndSet( [MASK] ) != [MASK] ) { if ( [MASK] <= 0) { stop(); // No more active monitoring lastTime.set(milliSecondFromNano()); } else { // Restart stop(); start(); } } } /** * Computes counters for Read. * * @param recv * the size in bytes to read */ void bytesRecvFlowControl(long recv) { currentReadBytes.addAndGet(recv); cumulativeReadBytes.addAndGet(recv); } /** * Computes counters for Write. * * @param write * the size in bytes to write */ void bytesWriteFlowControl(long write) { currentWrittenBytes.addAndGet(write); cumulativeWrittenBytes.addAndGet(write); } /** * Computes counters for Real Write. * * @param write * the size in bytes to write */ void bytesRealWriteFlowControl(long write) { realWrittenBytes.addAndGet(write); } /** * @return the current checkInterval between two computations of traffic counter * in millisecond. */ public long checkInterval() { return checkInterval.get(); } /** * @return the Read Throughput in bytes/s computes in the last check interval. */ public long lastReadThroughput() { return lastReadThroughput; } /** * @return the Write Throughput in bytes/s computes in the last check interval. */ public long lastWriteThroughput() { return lastWriteThroughput; } /** * @return the number of bytes read during the last check Interval. */ public long lastReadBytes() { return lastReadBytes; } /** * @return the number of bytes written during the last check Interval. */ public long lastWrittenBytes() { return lastWrittenBytes; } /** * @return the current number of bytes read since the last checkInterval. */ public long currentReadBytes() { return currentReadBytes.get(); } /** * @return the current number of bytes written since the last check Interval. */ public long currentWrittenBytes() { return currentWrittenBytes.get(); } /** * @return the Time in millisecond of the last check as of System.currentTimeMillis(). */ public long lastTime() { return lastTime.get(); } /** * @return the cumulativeWrittenBytes */ public long cumulativeWrittenBytes() { return cumulativeWrittenBytes.get(); } /** * @return the cumulativeReadBytes */ public long cumulativeReadBytes() { return cumulativeReadBytes.get(); } /** * @return the lastCumulativeTime in millisecond as of System.currentTimeMillis() * when the cumulative counters were reset to 0. */ public long lastCumulativeTime() { return lastCumulativeTime; } /** * @return the realWrittenBytes */ public AtomicLong getRealWrittenBytes() { return realWrittenBytes; } /** * @return the realWriteThroughput */ public long getRealWriteThroughput() { return realWriteThroughput; } /** * Reset both read and written cumulative bytes counters and the associated absolute time * from System.currentTimeMillis(). */ public void resetCumulativeTime() { lastCumulativeTime = System.currentTimeMillis(); cumulativeReadBytes.set(0); cumulativeWrittenBytes.set(0); } /** * @return the name of this TrafficCounter. */ public String name() { return name; } /** * Returns the time to wait (if any) for the given length message, using the given limitTraffic and the max wait * time. * * @param size * the recv size * @param limitTraffic * the traffic limit in bytes per second. * @param maxTime * the max time in ms to wait in case of excess of traffic. * @return the current time to wait (in ms) if needed for Read operation. */ @Deprecated public long readTimeToWait(final long size, final long limitTraffic, final long maxTime) { return readTimeToWait(size, limitTraffic, maxTime, milliSecondFromNano()); } /** * Returns the time to wait (if any) for the given length message, using the given limitTraffic and the max wait * time. * * @param size * the recv size * @param limitTraffic * the traffic limit in bytes per second * @param maxTime * the max time in ms to wait in case of excess of traffic. * @param now the current time * @return the current time to wait (in ms) if needed for Read operation. */ public long readTimeToWait(final long size, final long limitTraffic, final long maxTime, final long now) { bytesRecvFlowControl(size); if (size == 0 || limitTraffic == 0) { return 0; } final long lastTimeCheck = lastTime.get(); long sum = currentReadBytes.get(); long localReadingTime = readingTime; long lastRB = lastReadBytes; final long interval = now - lastTimeCheck; long pastDelay = Math.max(lastReadingTime - lastTimeCheck, 0); if (interval > AbstractTrafficShapingHandler.MINIMAL_WAIT) { // Enough interval time to compute shaping long time = sum * 1000 / limitTraffic - interval + pastDelay; if (time > AbstractTrafficShapingHandler.MINIMAL_WAIT) { if (logger.isDebugEnabled()) { logger.debug(""Time: "" + time + ':' + sum + ':' + interval + ':' + pastDelay); } if (time > maxTime && now + time - localReadingTime > maxTime) { time = maxTime; } readingTime = Math.max(localReadingTime, now + time); return time; } readingTime = Math.max(localReadingTime, now); return 0; } // take the last read interval check to get enough interval time long lastsum = sum + lastRB; long lastinterval = interval + checkInterval.get(); long time = lastsum * 1000 / limitTraffic - lastinterval + pastDelay; if (time > AbstractTrafficShapingHandler.MINIMAL_WAIT) { if (logger.isDebugEnabled()) { logger.debug(""Time: "" + time + ':' + lastsum + ':' + lastinterval + ':' + pastDelay); } if (time > maxTime && now + time - localReadingTime > maxTime) { time = maxTime; } readingTime = Math.max(localReadingTime, now + time); return time; } readingTime = Math.max(localReadingTime, now); return 0; } /** * Returns the time to wait (if any) for the given length message, using the given limitTraffic and * the max wait time. * * @param size * the write size * @param limitTraffic * the traffic limit in bytes per second. * @param maxTime * the max time in ms to wait in case of excess of traffic. * @return the current time to wait (in ms) if needed for Write operation. */ @Deprecated public long writeTimeToWait(final long size, final long limitTraffic, final long maxTime) { return writeTimeToWait(size, limitTraffic, maxTime, milliSecondFromNano()); } /** * Returns the time to wait (if any) for the given length message, using the given limitTraffic and * the max wait time. * * @param size * the write size * @param limitTraffic * the traffic limit in bytes per second. * @param maxTime * the max time in ms to wait in case of excess of traffic. * @param now the current time * @return the current time to wait (in ms) if needed for Write operation. */ public long writeTimeToWait(final long size, final long limitTraffic, final long maxTime, final long now) { bytesWriteFlowControl(size); if (size == 0 || limitTraffic == 0) { return 0; } final long lastTimeCheck = lastTime.get(); long sum = currentWrittenBytes.get(); long lastWB = lastWrittenBytes; long localWritingTime = writingTime; long pastDelay = Math.max(lastWritingTime - lastTimeCheck, 0); final long interval = now - lastTimeCheck; if (interval > AbstractTrafficShapingHandler.MINIMAL_WAIT) { // Enough interval time to compute shaping long time = sum * 1000 / limitTraffic - interval + pastDelay; if (time > AbstractTrafficShapingHandler.MINIMAL_WAIT) { if (logger.isDebugEnabled()) { logger.debug(""Time: "" + time + ':' + sum + ':' + interval + ':' + pastDelay); } if (time > maxTime && now + time - localWritingTime > maxTime) { time = maxTime; } writingTime = Math.max(localWritingTime, now + time); return time; } writingTime = Math.max(localWritingTime, now); return 0; } // take the last write interval check to get enough interval time long lastsum = sum + lastWB; long lastinterval = interval + checkInterval.get(); long time = lastsum * 1000 / limitTraffic - lastinterval + pastDelay; if (time > AbstractTrafficShapingHandler.MINIMAL_WAIT) { if (logger.isDebugEnabled()) { logger.debug(""Time: "" + time + ':' + lastsum + ':' + lastinterval + ':' + pastDelay); } if (time > maxTime && now + time - localWritingTime > maxTime) { time = maxTime; } writingTime = Math.max(localWritingTime, now + time); return time; } writingTime = Math.max(localWritingTime, now); return 0; } @Override public String toString() { return new StringBuilder(165).append(""Monitor "").append(name) .append("" Current Speed Read: "").append(lastReadThroughput >> 10).append("" KB/s, "") .append(""Asked Write: "").append(lastWriteThroughput >> 10).append("" KB/s, "") .append(""Real Write: "").append(realWriteThroughput >> 10).append("" KB/s, "") .append(""Current Read: "").append(currentReadBytes.get() >> 10).append("" KB, "") .append(""Current asked Write: "").append(currentWrittenBytes.get() >> 10).append("" KB, "") .append(""Current real Write: "").append(realWrittenBytes.get() >> 10).append("" KB"").toString(); } } ","newInterval " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.rpc.cluster.router.condition.config.model; import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static org.apache.dubbo.rpc.cluster.Constants.CONDITIONS_KEY; public class ConditionRouterRule extends AbstractRouterRule { private List [MASK] ; @SuppressWarnings(""unchecked"") public static ConditionRouterRule parseFromMap(Map map) { ConditionRouterRule conditionRouterRule = new ConditionRouterRule(); conditionRouterRule.parseFromMap0(map); Object [MASK] = map.get(CONDITIONS_KEY); if ( [MASK] != null && List.class.isAssignableFrom( [MASK] .getClass())) { conditionRouterRule.setConditions(((List) [MASK] ).stream() .map(String::valueOf).collect(Collectors.toList())); } return conditionRouterRule; } public ConditionRouterRule() { } public List getConditions() { return [MASK] ; } public void setConditions(List [MASK] ) { this. [MASK] = [MASK] ; } } ","conditions " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package libcore.java.util; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.SimpleTimeZone; import java.util.TimeZone; import junit.framework.TestCase; /** * Tests for {@link SimpleTimeZone}. * *

The methods starting {@code testDstParis2014_...} and {@code testDstNewYork2014} check * various different ways to specify the same instants when DST starts and ends in the associated * real world time zone in 2014, i.e. Europe/Paris and America/New_York respectively. */ public class SimpleTimeZoneTest extends TestCase { private static final int NEW_YORK_RAW_OFFSET = -18000000; private static final int PARIS_RAW_OFFSET = 3600000; private static final TimeZone UTC = TimeZone.getTimeZone(""UTC""); /** * Sanity check to ensure that the standard TimeZone for Europe/Paris has the correct DST * transition times. */ public void testStandardParis2014() { TimeZone timeZone = TimeZone.getTimeZone(""Europe/Paris""); checkDstParis2014(timeZone); } public void testDstParis2014_LastSundayMarch_LastSundayOctober_UtcTime() { TimeZone timeZone = new SimpleTimeZone(PARIS_RAW_OFFSET, ""Europe/Paris"", Calendar.MARCH, -1, Calendar.SUNDAY, 3600000, SimpleTimeZone.UTC_TIME, Calendar.OCTOBER, -1, Calendar.SUNDAY, 3600000, SimpleTimeZone.UTC_TIME, 3600000); checkDstParis2014(timeZone); } public void testDstParis2014_SundayAfter25thMarch_SundayAfter25thOctober_UtcTime() { TimeZone timeZone = new SimpleTimeZone(PARIS_RAW_OFFSET, ""Europe/Paris"", Calendar.MARCH, 25, -Calendar.SUNDAY, 3600000, SimpleTimeZone.UTC_TIME, Calendar.OCTOBER, 25, -Calendar.SUNDAY, 3600000, SimpleTimeZone.UTC_TIME, 3600000); checkDstParis2014(timeZone); } public void testDstParis2014_30thMarch_26thOctober_UtcTime() { TimeZone timeZone = new SimpleTimeZone(PARIS_RAW_OFFSET, ""Europe/Paris"", Calendar.MARCH, 30, 0, 3600000, SimpleTimeZone.UTC_TIME, Calendar.OCTOBER, 26, 0, 3600000, SimpleTimeZone.UTC_TIME, 3600000); checkDstParis2014(timeZone); } /** * Check that the DST transitions in the supplied {@link TimeZone} are as expected for * Europe/Paris in 2014. */ private void checkDstParis2014(TimeZone timeZone) { checkDstTransitionTimes(timeZone, 2014, ""2014-03-30T01:00:00.000+0000"", ""2014-10-26T01:00:00.000+0000""); } public void testDst_1stSundayApril_1stSundayOctober_DefaultTime() { TimeZone timeZone = new SimpleTimeZone(-18000000, ""EST"", Calendar.APRIL, 1, -Calendar.SUNDAY, 7200000, Calendar.OCTOBER, -1, Calendar.SUNDAY, 7200000, 3600000); checkDstTransitionTimes(timeZone, 1998, ""1998-04-05T07:00:00.000+0000"", ""1998-10-25T06:00:00.000+0000""); checkDstTransitionTimes(timeZone, 2014, ""2014-04-06T07:00:00.000+0000"", ""2014-10-26T06:00:00.000+0000""); } /** * Sanity check to ensure that the standard TimeZone for America/New_York has the correct DST * transition times. */ public void testStandardNewYork2014() { TimeZone timeZone = TimeZone.getTimeZone(""America/New_York""); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_2ndSundayMarch_1stSundayNovember_StandardTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 2, Calendar.SUNDAY, 7200000, SimpleTimeZone.STANDARD_TIME, Calendar.NOVEMBER, 1, Calendar.SUNDAY, 3600000, SimpleTimeZone.STANDARD_TIME, 3600000); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_2ndSundayMarch_1stSundayNovember_UtcTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 2, Calendar.SUNDAY, 25200000, SimpleTimeZone.UTC_TIME, Calendar.NOVEMBER, 1, Calendar.SUNDAY, 21600000, SimpleTimeZone.UTC_TIME, 3600000); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_2ndSundayMarch_1stSundayNovember_WallTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 2, Calendar.SUNDAY, 7200000, SimpleTimeZone.WALL_TIME, Calendar.NOVEMBER, 1, Calendar.SUNDAY, 7200000, SimpleTimeZone.WALL_TIME, 3600000); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_2ndSundayMarch_1stSundayNovember_DefaultTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 2, Calendar.SUNDAY, 7200000, Calendar.NOVEMBER, 1, Calendar.SUNDAY, 7200000, 3600000); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_9thMarch_2ndNovember_StandardTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 9, 0, 7200000, SimpleTimeZone.STANDARD_TIME, Calendar.NOVEMBER, 2, 0, 3600000, SimpleTimeZone.STANDARD_TIME, 3600000); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_9thMarch_2ndNovember_UtcTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 9, 0, 25200000, SimpleTimeZone.UTC_TIME, Calendar.NOVEMBER, 2, 0, 21600000, SimpleTimeZone.UTC_TIME, 3600000); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_9thMarch_2ndNovember_WallTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 9, 0, 7200000, SimpleTimeZone.WALL_TIME, Calendar.NOVEMBER, 2, 0, 7200000, SimpleTimeZone.WALL_TIME, 3600000); checkDstNewYork2014(timeZone); } public void testDstNewYork2014_9thMarch_2ndNovember_DefaultTime() { TimeZone timeZone = new SimpleTimeZone(NEW_YORK_RAW_OFFSET, ""EST"", Calendar.MARCH, 9, 0, 7200000, Calendar.NOVEMBER, 2, 0, 7200000, 3600000); checkDstNewYork2014(timeZone); } /** * Check that the DST transitions in the supplied {@link TimeZone} are as expected for * America/New_York in 2014. */ private void checkDstNewYork2014(TimeZone timeZone) { checkDstTransitionTimes(timeZone, 2014, ""2014-03-09T07:00:00.000+0000"", ""2014-11-02T06:00:00.000+0000""); } /** * Scan from the start of the year to the end to find the DST transition points. * * @param timeZone the {@link TimeZone} whose transition points are being found. * @param startOfYearMillis the start of the calendar year in {@code timeZone} to scan, in * milliseconds. * @return an array of the entry and exit time in millis. */ private static long[] findDstEntryAndExit(TimeZone timeZone, long startOfYearMillis) { if (!timeZone.useDaylightTime()) { throw new IllegalStateException(""Time zone "" + timeZone + "" doesn't support daylight savings time""); } long[] transitions = new long[2]; GregorianCalendar cal = new GregorianCalendar(timeZone, Locale.ENGLISH); cal.setTimeInMillis(startOfYearMillis); int year = cal.get(Calendar.YEAR); while (!timeZone.inDaylightTime(new Date(cal.getTimeInMillis()))) { // Make sure that this doesn't loop forever. if (cal.get(Calendar.YEAR) != year) { throw new IllegalStateException( ""Doesn't enter daylight savings time in "" + year + "" in "" + timeZone); } cal.add(Calendar.HOUR_OF_DAY, 1); } cal.add(Calendar.MILLISECOND, -1); assertFalse(timeZone.inDaylightTime(cal.getTime())); cal.add(Calendar.MILLISECOND, 1); long [MASK] = cal.getTimeInMillis(); while (timeZone.inDaylightTime(new Date(cal.getTimeInMillis()))) { if (cal.get(Calendar.YEAR) != year) { throw new IllegalStateException( ""Doesn't exit daylight savings time in "" + year + "" in "" + timeZone); } cal.add(Calendar.HOUR_OF_DAY, 1); } cal.add(Calendar.MILLISECOND, -1); assertTrue(timeZone.inDaylightTime(cal.getTime())); cal.add(Calendar.MILLISECOND, 1); long exitPoint = cal.getTimeInMillis(); transitions[0] = [MASK] ; transitions[1] = exitPoint; return transitions; } public static String formatCalendar(Calendar cal) { SimpleDateFormat format = new SimpleDateFormat(""yyyy-MM-dd'T'HH:mm:ss.SSSZ"", Locale.ENGLISH); format.setTimeZone(cal.getTimeZone()); return format.format(new Date(cal.getTimeInMillis())); } private static String formatTime(TimeZone timeZone, long millis) { Calendar cal = new GregorianCalendar(timeZone, Locale.ENGLISH); cal.setTimeInMillis(millis); return formatCalendar(cal); } private void checkDstTransitionTimes(TimeZone timeZone, int year, String expectedUtcEntryTime, String expectedUtcExitTime) { // Find the start of the year in the supplied time zone. GregorianCalendar calendar = new GregorianCalendar(timeZone, Locale.ENGLISH); calendar.set(year, Calendar.JANUARY, 1, 0, 0, 0); calendar.set(Calendar.MILLISECOND, 0); long start = calendar.getTimeInMillis(); // Find the DST transitions instants. long[] simpleTransitions = findDstEntryAndExit(timeZone, start); String actualUtcEntryTime = formatTime(UTC, simpleTransitions[0]); String actualUtcExitTime = formatTime(UTC, simpleTransitions[1]); assertEquals(""Transition point mismatch: "", describeTransitions(expectedUtcEntryTime, expectedUtcExitTime), describeTransitions(actualUtcEntryTime, actualUtcExitTime)); } /** * Create a string representation of the transition information to allow all aspects to be * compared in one go providing a better error message. */ private String describeTransitions(String utcEntryTime, String utcExitTime) { return ""{Entry: "" + utcEntryTime + "", Exit: "" + utcExitTime + ""}""; } } ","entryPoint " "package com.alibaba.json.test.benchmark; import java.lang.management.ManagementFactory; import java.util.List; import com.alibaba.fastjson.JSON; import com.alibaba.json.test.TestUtils; import com.alibaba.json.test.benchmark.decode.EishayDecodeBytes; import data.media.MediaContent; public class BenchmarkMain_EishayDecode { public static void main(String[] args) throws Exception { System.out.println(System.getProperty(""java.vm.name"") + "" "" + System.getProperty(""java.runtime.version"")); List [MASK] = ManagementFactory.getRuntimeMXBean().getInputArguments(); System.out.println( [MASK] ); String text = EishayDecodeBytes.instance.getText(); System.out.println(text); for (int i = 0; i < 10; ++i) { perf(text); } } static long perf(String text) { long startYGC = TestUtils.getYoungGC(); long startYGCTime = TestUtils.getYoungGCTime(); long startFGC = TestUtils.getFullGC(); long startMillis = System.currentTimeMillis(); for (int i = 0; i < 1000 * 1000; ++i) { decode(text); } long millis = System.currentTimeMillis() - startMillis; long ygc = TestUtils.getYoungGC() - startYGC; long ygct = TestUtils.getYoungGCTime() - startYGCTime; long fgc = TestUtils.getFullGC() - startFGC; System.out.println(""decode\t"" + millis + "", ygc "" + ygc + "", ygct "" + ygct + "", fgc "" + fgc); return millis; } static void decode(String text) { MediaContent content = JSON.parseObject(text, MediaContent.class); // JSON.parseObject(text); } } ","arguments " "/* * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2; import android.os.Looper; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import androidx.annotation.Nullable; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.util.Size; import com.google.android.exoplayer2.video.VideoSize; import java.util.List; /** * A {@link Player} that forwards method calls to another {@link Player}. Applications can use this * class to suppress or modify specific operations, by overriding the respective methods. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public class ForwardingPlayer implements Player { private final Player player; /** Creates a new instance that forwards all operations to {@code player}. */ public ForwardingPlayer(Player player) { this.player = player; } /** Calls {@link Player#getApplicationLooper()} on the delegate and returns the result. */ @Override public Looper getApplicationLooper() { return player.getApplicationLooper(); } /** * Calls {@link Player#addListener(Listener)} on the delegate. * *

Overrides of this method must not directly call {@code * delegate.addListener}. If the override wants to pass the {@link Player.Listener} instance to * the delegate {@link Player}, it must do so by calling {@code super.addListener} instead. This * ensures the correct {@link Player} instance is passed to {@link * Player.Listener#onEvents(Player, Events)} (i.e. this forwarding instance, and not the * underlying {@code delegate} instance). */ @Override public void addListener(Listener listener) { player.addListener(new ForwardingListener(this, listener)); } /** * Calls {@link Player#removeListener(Listener)} on the delegate. * *

Overrides of this method must not directly call {@code * delegate.removeListener}. If the override wants to pass the {@link Player.Listener} instance to * the delegate {@link Player}, it must do so by calling {@code super.removeListener} instead. */ @Override public void removeListener(Listener listener) { player.removeListener(new ForwardingListener(this, listener)); } /** Calls {@link Player#setMediaItems(List)} on the delegate. */ @Override public void setMediaItems(List mediaItems) { player.setMediaItems(mediaItems); } /** Calls {@link Player#setMediaItems(List, boolean)} ()} on the delegate. */ @Override public void setMediaItems(List mediaItems, boolean resetPosition) { player.setMediaItems(mediaItems, resetPosition); } /** Calls {@link Player#setMediaItems(List, int, long)} on the delegate. */ @Override public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { player.setMediaItems(mediaItems, startIndex, startPositionMs); } /** Calls {@link Player#setMediaItem(MediaItem)} on the delegate. */ @Override public void setMediaItem(MediaItem mediaItem) { player.setMediaItem(mediaItem); } /** Calls {@link Player#setMediaItem(MediaItem, long)} on the delegate. */ @Override public void setMediaItem(MediaItem mediaItem, long startPositionMs) { player.setMediaItem(mediaItem, startPositionMs); } /** Calls {@link Player#setMediaItem(MediaItem, boolean)} on the delegate. */ @Override public void setMediaItem(MediaItem mediaItem, boolean resetPosition) { player.setMediaItem(mediaItem, resetPosition); } /** Calls {@link Player#addMediaItem(MediaItem)} on the delegate. */ @Override public void addMediaItem(MediaItem mediaItem) { player.addMediaItem(mediaItem); } /** Calls {@link Player#addMediaItem(int, MediaItem)} on the delegate. */ @Override public void addMediaItem(int index, MediaItem mediaItem) { player.addMediaItem(index, mediaItem); } /** Calls {@link Player#addMediaItems(List)} on the delegate. */ @Override public void addMediaItems(List mediaItems) { player.addMediaItems(mediaItems); } /** Calls {@link Player#addMediaItems(int, List)} on the delegate. */ @Override public void addMediaItems(int index, List mediaItems) { player.addMediaItems(index, mediaItems); } /** Calls {@link Player#moveMediaItem(int, int)} on the delegate. */ @Override public void moveMediaItem(int currentIndex, int newIndex) { player.moveMediaItem(currentIndex, newIndex); } /** Calls {@link Player#moveMediaItems(int, int, int)} on the delegate. */ @Override public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { player.moveMediaItems(fromIndex, toIndex, newIndex); } /** Calls {@link Player#replaceMediaItem(int, MediaItem)} on the delegate. */ @Override public void replaceMediaItem(int index, MediaItem mediaItem) { player.replaceMediaItem(index, mediaItem); } /** Calls {@link Player#replaceMediaItems(int, int, List)} on the delegate. */ @Override public void replaceMediaItems(int fromIndex, int toIndex, List mediaItems) { player.replaceMediaItems(fromIndex, toIndex, mediaItems); } /** Calls {@link Player#removeMediaItem(int)} on the delegate. */ @Override public void removeMediaItem(int index) { player.removeMediaItem(index); } /** Calls {@link Player#removeMediaItems(int, int)} on the delegate. */ @Override public void removeMediaItems(int fromIndex, int toIndex) { player.removeMediaItems(fromIndex, toIndex); } /** Calls {@link Player#clearMediaItems()} on the delegate. */ @Override public void clearMediaItems() { player.clearMediaItems(); } /** Calls {@link Player#isCommandAvailable(int)} on the delegate and returns the result. */ @Override public boolean isCommandAvailable(@Command int command) { return player.isCommandAvailable(command); } /** Calls {@link Player#canAdvertiseSession()} on the delegate and returns the result. */ @Override public boolean canAdvertiseSession() { return player.canAdvertiseSession(); } /** Calls {@link Player#getAvailableCommands()} on the delegate and returns the result. */ @Override public Commands getAvailableCommands() { return player.getAvailableCommands(); } /** Calls {@link Player#prepare()} on the delegate. */ @Override public void prepare() { player.prepare(); } /** Calls {@link Player#getPlaybackState()} on the delegate and returns the result. */ @Override public int getPlaybackState() { return player.getPlaybackState(); } /** Calls {@link Player#getPlaybackSuppressionReason()} on the delegate and returns the result. */ @Override public int getPlaybackSuppressionReason() { return player.getPlaybackSuppressionReason(); } /** Calls {@link Player#isPlaying()} on the delegate and returns the result. */ @Override public boolean isPlaying() { return player.isPlaying(); } /** Calls {@link Player#getPlayerError()} on the delegate and returns the result. */ @Nullable @Override public PlaybackException getPlayerError() { return player.getPlayerError(); } /** Calls {@link Player#play()} on the delegate. */ @Override public void play() { player.play(); } /** Calls {@link Player#pause()} on the delegate. */ @Override public void pause() { player.pause(); } /** Calls {@link Player#setPlayWhenReady(boolean)} on the delegate. */ @Override public void setPlayWhenReady(boolean playWhenReady) { player.setPlayWhenReady(playWhenReady); } /** Calls {@link Player#getPlayWhenReady()} on the delegate and returns the result. */ @Override public boolean getPlayWhenReady() { return player.getPlayWhenReady(); } /** Calls {@link Player#setRepeatMode(int)} on the delegate. */ @Override public void setRepeatMode(@RepeatMode int repeatMode) { player.setRepeatMode(repeatMode); } /** Calls {@link Player#getRepeatMode()} on the delegate and returns the result. */ @Override public int getRepeatMode() { return player.getRepeatMode(); } /** Calls {@link Player#setShuffleModeEnabled(boolean)} on the delegate. */ @Override public void setShuffleModeEnabled(boolean [MASK] ) { player.setShuffleModeEnabled( [MASK] ); } /** Calls {@link Player#getShuffleModeEnabled()} on the delegate and returns the result. */ @Override public boolean getShuffleModeEnabled() { return player.getShuffleModeEnabled(); } /** Calls {@link Player#isLoading()} on the delegate and returns the result. */ @Override public boolean isLoading() { return player.isLoading(); } /** Calls {@link Player#seekToDefaultPosition()} on the delegate. */ @Override public void seekToDefaultPosition() { player.seekToDefaultPosition(); } /** Calls {@link Player#seekToDefaultPosition(int)} on the delegate. */ @Override public void seekToDefaultPosition(int mediaItemIndex) { player.seekToDefaultPosition(mediaItemIndex); } /** Calls {@link Player#seekTo(long)} on the delegate. */ @Override public void seekTo(long positionMs) { player.seekTo(positionMs); } /** Calls {@link Player#seekTo(int, long)} on the delegate. */ @Override public void seekTo(int mediaItemIndex, long positionMs) { player.seekTo(mediaItemIndex, positionMs); } /** Calls {@link Player#getSeekBackIncrement()} on the delegate and returns the result. */ @Override public long getSeekBackIncrement() { return player.getSeekBackIncrement(); } /** Calls {@link Player#seekBack()} on the delegate. */ @Override public void seekBack() { player.seekBack(); } /** Calls {@link Player#getSeekForwardIncrement()} on the delegate and returns the result. */ @Override public long getSeekForwardIncrement() { return player.getSeekForwardIncrement(); } /** Calls {@link Player#seekForward()} on the delegate. */ @Override public void seekForward() { player.seekForward(); } /** * Calls {@link Player#hasPrevious()} on the delegate and returns the result. * * @deprecated Use {@link #hasPreviousMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public boolean hasPrevious() { return player.hasPrevious(); } /** * Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result. * * @deprecated Use {@link #hasPreviousMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public boolean hasPreviousWindow() { return player.hasPreviousWindow(); } /** Calls {@link Player#hasPreviousMediaItem()} on the delegate and returns the result. */ @Override public boolean hasPreviousMediaItem() { return player.hasPreviousMediaItem(); } /** * Calls {@link Player#previous()} on the delegate. * * @deprecated Use {@link #seekToPreviousMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public void previous() { player.previous(); } /** * Calls {@link Player#seekToPreviousWindow()} on the delegate. * * @deprecated Use {@link #seekToPreviousMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public void seekToPreviousWindow() { player.seekToPreviousWindow(); } /** Calls {@link Player#seekToPreviousMediaItem()} on the delegate. */ @Override public void seekToPreviousMediaItem() { player.seekToPreviousMediaItem(); } /** Calls {@link Player#seekToPrevious()} on the delegate. */ @Override public void seekToPrevious() { player.seekToPrevious(); } /** Calls {@link Player#getMaxSeekToPreviousPosition()} on the delegate and returns the result. */ @Override public long getMaxSeekToPreviousPosition() { return player.getMaxSeekToPreviousPosition(); } /** * Calls {@link Player#hasNext()} on the delegate and returns the result. * * @deprecated Use {@link #hasNextMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public boolean hasNext() { return player.hasNext(); } /** * Calls {@link Player#hasNextWindow()} on the delegate and returns the result. * * @deprecated Use {@link #hasNextMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public boolean hasNextWindow() { return player.hasNextWindow(); } /** Calls {@link Player#hasNextMediaItem()} on the delegate and returns the result. */ @Override public boolean hasNextMediaItem() { return player.hasNextMediaItem(); } /** * Calls {@link Player#next()} on the delegate. * * @deprecated Use {@link #seekToNextMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public void next() { player.next(); } /** * Calls {@link Player#seekToNextWindow()} on the delegate. * * @deprecated Use {@link #seekToNextMediaItem()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public void seekToNextWindow() { player.seekToNextWindow(); } /** Calls {@link Player#seekToNextMediaItem()} on the delegate. */ @Override public void seekToNextMediaItem() { player.seekToNextMediaItem(); } /** Calls {@link Player#seekToNext()} on the delegate. */ @Override public void seekToNext() { player.seekToNext(); } /** Calls {@link Player#setPlaybackParameters(PlaybackParameters)} on the delegate. */ @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { player.setPlaybackParameters(playbackParameters); } /** Calls {@link Player#setPlaybackSpeed(float)} on the delegate. */ @Override public void setPlaybackSpeed(float speed) { player.setPlaybackSpeed(speed); } /** Calls {@link Player#getPlaybackParameters()} on the delegate and returns the result. */ @Override public PlaybackParameters getPlaybackParameters() { return player.getPlaybackParameters(); } /** Calls {@link Player#stop()} on the delegate. */ @Override public void stop() { player.stop(); } /** Calls {@link Player#release()} on the delegate. */ @Override public void release() { player.release(); } /** Calls {@link Player#getCurrentTracks()} on the delegate and returns the result. */ @Override public Tracks getCurrentTracks() { return player.getCurrentTracks(); } /** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */ @Override public TrackSelectionParameters getTrackSelectionParameters() { return player.getTrackSelectionParameters(); } /** Calls {@link Player#setTrackSelectionParameters(TrackSelectionParameters)} on the delegate. */ @Override public void setTrackSelectionParameters(TrackSelectionParameters parameters) { player.setTrackSelectionParameters(parameters); } /** Calls {@link Player#getMediaMetadata()} on the delegate and returns the result. */ @Override public MediaMetadata getMediaMetadata() { return player.getMediaMetadata(); } /** Calls {@link Player#getPlaylistMetadata()} on the delegate and returns the result. */ @Override public MediaMetadata getPlaylistMetadata() { return player.getPlaylistMetadata(); } /** Calls {@link Player#setPlaylistMetadata(MediaMetadata)} on the delegate. */ @Override public void setPlaylistMetadata(MediaMetadata mediaMetadata) { player.setPlaylistMetadata(mediaMetadata); } /** Calls {@link Player#getCurrentManifest()} on the delegate and returns the result. */ @Nullable @Override public Object getCurrentManifest() { return player.getCurrentManifest(); } /** Calls {@link Player#getCurrentTimeline()} on the delegate and returns the result. */ @Override public Timeline getCurrentTimeline() { return player.getCurrentTimeline(); } /** Calls {@link Player#getCurrentPeriodIndex()} on the delegate and returns the result. */ @Override public int getCurrentPeriodIndex() { return player.getCurrentPeriodIndex(); } /** * Calls {@link Player#getCurrentWindowIndex()} on the delegate and returns the result. * * @deprecated Use {@link #getCurrentMediaItemIndex()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public int getCurrentWindowIndex() { return player.getCurrentWindowIndex(); } /** Calls {@link Player#getCurrentMediaItemIndex()} on the delegate and returns the result. */ @Override public int getCurrentMediaItemIndex() { return player.getCurrentMediaItemIndex(); } /** * Calls {@link Player#getNextWindowIndex()} on the delegate and returns the result. * * @deprecated Use {@link #getNextMediaItemIndex()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public int getNextWindowIndex() { return player.getNextWindowIndex(); } /** Calls {@link Player#getNextMediaItemIndex()} on the delegate and returns the result. */ @Override public int getNextMediaItemIndex() { return player.getNextMediaItemIndex(); } /** * Calls {@link Player#getPreviousWindowIndex()} on the delegate and returns the result. * * @deprecated Use {@link #getPreviousMediaItemIndex()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public int getPreviousWindowIndex() { return player.getPreviousWindowIndex(); } /** Calls {@link Player#getPreviousMediaItemIndex()} on the delegate and returns the result. */ @Override public int getPreviousMediaItemIndex() { return player.getPreviousMediaItemIndex(); } /** Calls {@link Player#getCurrentMediaItem()} on the delegate and returns the result. */ @Nullable @Override public MediaItem getCurrentMediaItem() { return player.getCurrentMediaItem(); } /** Calls {@link Player#getMediaItemCount()} on the delegate and returns the result. */ @Override public int getMediaItemCount() { return player.getMediaItemCount(); } /** Calls {@link Player#getMediaItemAt(int)} on the delegate and returns the result. */ @Override public MediaItem getMediaItemAt(int index) { return player.getMediaItemAt(index); } /** Calls {@link Player#getDuration()} on the delegate and returns the result. */ @Override public long getDuration() { return player.getDuration(); } /** Calls {@link Player#getCurrentPosition()} on the delegate and returns the result. */ @Override public long getCurrentPosition() { return player.getCurrentPosition(); } /** Calls {@link Player#getBufferedPosition()} on the delegate and returns the result. */ @Override public long getBufferedPosition() { return player.getBufferedPosition(); } /** Calls {@link Player#getBufferedPercentage()} on the delegate and returns the result. */ @Override public int getBufferedPercentage() { return player.getBufferedPercentage(); } /** Calls {@link Player#getTotalBufferedDuration()} on the delegate and returns the result. */ @Override public long getTotalBufferedDuration() { return player.getTotalBufferedDuration(); } /** * Calls {@link Player#isCurrentWindowDynamic()} on the delegate and returns the result. * * @deprecated Use {@link #isCurrentMediaItemDynamic()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public boolean isCurrentWindowDynamic() { return player.isCurrentWindowDynamic(); } /** Calls {@link Player#isCurrentMediaItemDynamic()} on the delegate and returns the result. */ @Override public boolean isCurrentMediaItemDynamic() { return player.isCurrentMediaItemDynamic(); } /** * Calls {@link Player#isCurrentWindowLive()} on the delegate and returns the result. * * @deprecated Use {@link #isCurrentMediaItemLive()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public boolean isCurrentWindowLive() { return player.isCurrentWindowLive(); } /** Calls {@link Player#isCurrentMediaItemLive()} on the delegate and returns the result. */ @Override public boolean isCurrentMediaItemLive() { return player.isCurrentMediaItemLive(); } /** Calls {@link Player#getCurrentLiveOffset()} on the delegate and returns the result. */ @Override public long getCurrentLiveOffset() { return player.getCurrentLiveOffset(); } /** * Calls {@link Player#isCurrentWindowSeekable()} on the delegate and returns the result. * * @deprecated Use {@link #isCurrentMediaItemSeekable()} instead. */ @SuppressWarnings(""deprecation"") // Forwarding to deprecated method @Deprecated @Override public boolean isCurrentWindowSeekable() { return player.isCurrentWindowSeekable(); } /** Calls {@link Player#isCurrentMediaItemSeekable()} on the delegate and returns the result. */ @Override public boolean isCurrentMediaItemSeekable() { return player.isCurrentMediaItemSeekable(); } /** Calls {@link Player#isPlayingAd()} on the delegate and returns the result. */ @Override public boolean isPlayingAd() { return player.isPlayingAd(); } /** Calls {@link Player#getCurrentAdGroupIndex()} on the delegate and returns the result. */ @Override public int getCurrentAdGroupIndex() { return player.getCurrentAdGroupIndex(); } /** Calls {@link Player#getCurrentAdIndexInAdGroup()} on the delegate and returns the result. */ @Override public int getCurrentAdIndexInAdGroup() { return player.getCurrentAdIndexInAdGroup(); } /** Calls {@link Player#getContentDuration()} on the delegate and returns the result. */ @Override public long getContentDuration() { return player.getContentDuration(); } /** Calls {@link Player#getContentPosition()} on the delegate and returns the result. */ @Override public long getContentPosition() { return player.getContentPosition(); } /** Calls {@link Player#getContentBufferedPosition()} on the delegate and returns the result. */ @Override public long getContentBufferedPosition() { return player.getContentBufferedPosition(); } /** Calls {@link Player#getAudioAttributes()} on the delegate and returns the result. */ @Override public AudioAttributes getAudioAttributes() { return player.getAudioAttributes(); } /** Calls {@link Player#setVolume(float)} on the delegate. */ @Override public void setVolume(float volume) { player.setVolume(volume); } /** Calls {@link Player#getVolume()} on the delegate and returns the result. */ @Override public float getVolume() { return player.getVolume(); } /** Calls {@link Player#getVideoSize()} on the delegate and returns the result. */ @Override public VideoSize getVideoSize() { return player.getVideoSize(); } /** Calls {@link Player#getSurfaceSize()} on the delegate and returns the result. */ @Override public Size getSurfaceSize() { return player.getSurfaceSize(); } /** Calls {@link Player#clearVideoSurface()} on the delegate. */ @Override public void clearVideoSurface() { player.clearVideoSurface(); } /** Calls {@link Player#clearVideoSurface(Surface)} on the delegate. */ @Override public void clearVideoSurface(@Nullable Surface surface) { player.clearVideoSurface(surface); } /** Calls {@link Player#setVideoSurface(Surface)} on the delegate. */ @Override public void setVideoSurface(@Nullable Surface surface) { player.setVideoSurface(surface); } /** Calls {@link Player#setVideoSurfaceHolder(SurfaceHolder)} on the delegate. */ @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { player.setVideoSurfaceHolder(surfaceHolder); } /** Calls {@link Player#clearVideoSurfaceHolder(SurfaceHolder)} on the delegate. */ @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { player.clearVideoSurfaceHolder(surfaceHolder); } /** Calls {@link Player#setVideoSurfaceView(SurfaceView)} on the delegate. */ @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { player.setVideoSurfaceView(surfaceView); } /** Calls {@link Player#clearVideoSurfaceView(SurfaceView)} on the delegate. */ @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { player.clearVideoSurfaceView(surfaceView); } /** Calls {@link Player#setVideoTextureView(TextureView)} on the delegate. */ @Override public void setVideoTextureView(@Nullable TextureView textureView) { player.setVideoTextureView(textureView); } /** Calls {@link Player#clearVideoTextureView(TextureView)} on the delegate. */ @Override public void clearVideoTextureView(@Nullable TextureView textureView) { player.clearVideoTextureView(textureView); } /** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */ @Override public CueGroup getCurrentCues() { return player.getCurrentCues(); } /** Calls {@link Player#getDeviceInfo()} on the delegate and returns the result. */ @Override public DeviceInfo getDeviceInfo() { return player.getDeviceInfo(); } /** Calls {@link Player#getDeviceVolume()} on the delegate and returns the result. */ @Override public int getDeviceVolume() { return player.getDeviceVolume(); } /** Calls {@link Player#isDeviceMuted()} on the delegate and returns the result. */ @Override public boolean isDeviceMuted() { return player.isDeviceMuted(); } /** * @deprecated Use {@link #setDeviceVolume(int, int)} instead. */ @Deprecated @Override public void setDeviceVolume(int volume) { player.setDeviceVolume(volume); } /** Calls {@link Player#setDeviceVolume(int, int)} on the delegate. */ @Override public void setDeviceVolume(int volume, @C.VolumeFlags int flags) { player.setDeviceVolume(volume, flags); } /** * @deprecated Use {@link #increaseDeviceVolume(int)} instead. */ @Deprecated @Override public void increaseDeviceVolume() { player.increaseDeviceVolume(); } /** Calls {@link Player#increaseDeviceVolume(int)} on the delegate. */ @Override public void increaseDeviceVolume(@C.VolumeFlags int flags) { player.increaseDeviceVolume(flags); } /** * @deprecated Use {@link #decreaseDeviceVolume(int)} instead. */ @Deprecated @Override public void decreaseDeviceVolume() { player.decreaseDeviceVolume(); } /** Calls {@link Player#decreaseDeviceVolume(int)} on the delegate. */ @Override public void decreaseDeviceVolume(@C.VolumeFlags int flags) { player.decreaseDeviceVolume(flags); } /** * @deprecated Use {@link #setDeviceMuted(boolean, int)} instead. */ @Deprecated @Override public void setDeviceMuted(boolean muted) { player.setDeviceMuted(muted); } /** Calls {@link Player#setDeviceMuted(boolean, int)} on the delegate. */ @Override public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) { player.setDeviceMuted(muted, flags); } /** Returns the {@link Player} to which operations are forwarded. */ public Player getWrappedPlayer() { return player; } private static final class ForwardingListener implements Listener { private final ForwardingPlayer forwardingPlayer; private final Listener listener; public ForwardingListener(ForwardingPlayer forwardingPlayer, Listener listener) { this.forwardingPlayer = forwardingPlayer; this.listener = listener; } @Override public void onEvents(Player player, Events events) { // Replace player with forwarding player. listener.onEvents(forwardingPlayer, events); } @Override public void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) { listener.onTimelineChanged(timeline, reason); } @Override public void onMediaItemTransition( @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) { listener.onMediaItemTransition(mediaItem, reason); } @Override public void onTracksChanged(Tracks tracks) { listener.onTracksChanged(tracks); } @Override public void onMediaMetadataChanged(MediaMetadata mediaMetadata) { listener.onMediaMetadataChanged(mediaMetadata); } @Override public void onPlaylistMetadataChanged(MediaMetadata mediaMetadata) { listener.onPlaylistMetadataChanged(mediaMetadata); } @Override public void onIsLoadingChanged(boolean isLoading) { listener.onIsLoadingChanged(isLoading); } @Override @SuppressWarnings(""deprecation"") public void onLoadingChanged(boolean isLoading) { listener.onIsLoadingChanged(isLoading); } @Override public void onAvailableCommandsChanged(Commands availableCommands) { listener.onAvailableCommandsChanged(availableCommands); } @Override public void onTrackSelectionParametersChanged(TrackSelectionParameters parameters) { listener.onTrackSelectionParametersChanged(parameters); } @Override @SuppressWarnings(""deprecation"") public void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) { listener.onPlayerStateChanged(playWhenReady, playbackState); } @Override public void onPlaybackStateChanged(@State int playbackState) { listener.onPlaybackStateChanged(playbackState); } @Override public void onPlayWhenReadyChanged( boolean playWhenReady, @PlayWhenReadyChangeReason int reason) { listener.onPlayWhenReadyChanged(playWhenReady, reason); } @Override public void onPlaybackSuppressionReasonChanged( @PlayWhenReadyChangeReason int playbackSuppressionReason) { listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason); } @Override public void onIsPlayingChanged(boolean isPlaying) { listener.onIsPlayingChanged(isPlaying); } @Override public void onRepeatModeChanged(@RepeatMode int repeatMode) { listener.onRepeatModeChanged(repeatMode); } @Override public void onShuffleModeEnabledChanged(boolean [MASK] ) { listener.onShuffleModeEnabledChanged( [MASK] ); } @Override public void onPlayerError(PlaybackException error) { listener.onPlayerError(error); } @Override public void onPlayerErrorChanged(@Nullable PlaybackException error) { listener.onPlayerErrorChanged(error); } @Override @SuppressWarnings(""deprecation"") public void onPositionDiscontinuity(@DiscontinuityReason int reason) { listener.onPositionDiscontinuity(reason); } @Override public void onPositionDiscontinuity( PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) { listener.onPositionDiscontinuity(oldPosition, newPosition, reason); } @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { listener.onPlaybackParametersChanged(playbackParameters); } @Override public void onSeekBackIncrementChanged(long seekBackIncrementMs) { listener.onSeekBackIncrementChanged(seekBackIncrementMs); } @Override public void onSeekForwardIncrementChanged(long seekForwardIncrementMs) { listener.onSeekForwardIncrementChanged(seekForwardIncrementMs); } @Override public void onMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs) { listener.onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs); } @Override public void onVideoSizeChanged(VideoSize videoSize) { listener.onVideoSizeChanged(videoSize); } @Override public void onSurfaceSizeChanged(int width, int height) { listener.onSurfaceSizeChanged(width, height); } @Override public void onRenderedFirstFrame() { listener.onRenderedFirstFrame(); } @Override public void onAudioSessionIdChanged(int audioSessionId) { listener.onAudioSessionIdChanged(audioSessionId); } @Override public void onAudioAttributesChanged(AudioAttributes audioAttributes) { listener.onAudioAttributesChanged(audioAttributes); } @Override public void onVolumeChanged(float volume) { listener.onVolumeChanged(volume); } @Override public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) { listener.onSkipSilenceEnabledChanged(skipSilenceEnabled); } @Override public void onCues(List cues) { listener.onCues(cues); } @Override public void onCues(CueGroup cueGroup) { listener.onCues(cueGroup); } @Override public void onMetadata(Metadata metadata) { listener.onMetadata(metadata); } @Override public void onDeviceInfoChanged(DeviceInfo deviceInfo) { listener.onDeviceInfoChanged(deviceInfo); } @Override public void onDeviceVolumeChanged(int volume, boolean muted) { listener.onDeviceVolumeChanged(volume, muted); } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (!(o instanceof ForwardingListener)) { return false; } ForwardingListener that = (ForwardingListener) o; if (!forwardingPlayer.equals(that.forwardingPlayer)) { return false; } return listener.equals(that.listener); } @Override public int hashCode() { int result = forwardingPlayer.hashCode(); result = 31 * result + listener.hashCode(); return result; } } } ","shuffleModeEnabled " "/* * Copyright (C) 2014 The Android Open Source Project * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the ""Classpath"" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved * * The original version of this [MASK] code and documentation is copyrighted * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These * materials are provided under terms of a License Agreement between Taligent * and Sun. This technology is protected by multiple US and International * patents. This notice and attribution to Taligent may not be removed. * Taligent is a registered trademark of Taligent, Inc. * */ package java.text; import java.io.InvalidObjectException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.math.BigInteger; import java.math.RoundingMode; //import java.text.spi.NumberFormatProvider; import java.util.Currency; import java.util.HashMap; import java.util.Hashtable; import java.util.Locale; import java.util.Map; import java.util.Re [MASK] Bundle; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; //import java.util.spi.LocaleServiceProvider; import libcore.icu.ICU; import libcore.icu.LocaleData; //import sun.util.LocaleServiceProviderPool; // Android-removed: Remove javadoc related to ""rg"" Locale extension. // The ""rg"" extension isn't supported until https://unicode-org.atlassian.net/browse/ICU-21831 // is resolved, because java.text.* stack relies on ICU on re [MASK] resolution. /** * NumberFormat is the abstract base class for all number * formats. This class provides the interface for formatting and parsing * numbers. NumberFormat also provides methods for determining * which locales have number formats, and what their names are. * *

* NumberFormat helps you to format and parse numbers for any locale. * Your code can be completely independent of the locale conventions for * decimal points, thousands-separators, or even the particular decimal * digits used, or whether the number format is even decimal. * *

* To format a number for the current Locale, use one of the factory * class methods: *

*
{@code
 * myString = NumberFormat.getInstance().format(myNumber);
 * }
*
* If you are formatting multiple numbers, it is * more efficient to get the format and use it multiple times so that * the system doesn't have to fetch the information about the local * language and country conventions multiple times. *
*
{@code
 * NumberFormat nf = NumberFormat.getInstance();
 * for (int i = 0; i < myNumber.length; ++i) {
 *     output.println(nf.format(myNumber[i]) + ""; "");
 * }
 * }
*
* To format a number for a different Locale, specify it in the * call to getInstance. *
*
{@code
 * NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
 * }
*
* *

If the locale contains ""nu"" (numbers) * Unicode extensions, * the decimal digits, and/or the country used for formatting are overridden. * *

You can also use a {@code NumberFormat} to parse numbers: *

*
{@code
 * myNumber = nf.parse(myString);
 * }
*
* Use getInstance or getNumberInstance to get the * normal number format. Use getIntegerInstance to get an * integer number format. Use getCurrencyInstance to get the * currency number format. And use getPercentInstance to get a * format for displaying percentages. With this format, a fraction like * 0.53 is displayed as 53%. * *

* You can also control the display of numbers with such methods as * setMinimumFractionDigits. * If you want even more control over the format or parsing, * or want to give your users more control, * you can try casting the NumberFormat you get from the factory methods * to a DecimalFormat. This will work for the vast majority * of locales; just remember to put it in a try block in case you * encounter an unusual one. * *

* NumberFormat and DecimalFormat are designed such that some controls * work for formatting and others work for parsing. The following is * the detailed description for each these control methods, *

* setParseIntegerOnly : only affects parsing, e.g. * if true, ""3456.78"" → 3456 (and leaves the parse position just after index 6) * if false, ""3456.78"" → 3456.78 (and leaves the parse position just after index 8) * This is independent of formatting. If you want to not show a decimal point * where there might be no digits after the decimal point, use * setDecimalSeparatorAlwaysShown. *

* setDecimalSeparatorAlwaysShown : only affects formatting, and only where * there might be no digits after the decimal point, such as with a pattern * like ""#,##0.##"", e.g., * if true, 3456.00 → ""3,456."" * if false, 3456.00 → ""3456"" * This is independent of parsing. If you want parsing to stop at the decimal * point, use setParseIntegerOnly. * *

* You can also use forms of the parse and format * methods with ParsePosition and FieldPosition to * allow you to: *

    *
  • progressively parse through pieces of a string *
  • align the decimal point and other areas *
* For example, you can align numbers in two ways: *
    *
  1. If you are using a monospaced font with spacing for alignment, * you can pass the FieldPosition in your format call, with * field = INTEGER_FIELD. On output, * getEndIndex will be set to the offset between the * last character of the integer and the decimal. Add * (desiredSpaceCount - getEndIndex) spaces at the front of the string. * *
  2. If you are using proportional fonts, * instead of padding with spaces, measure the width * of the string in pixels from the start to getEndIndex. * Then move the pen by * (desiredPixelWidth - widthToAlignmentPoint) before drawing the text. * It also works where there is no decimal, but possibly additional * characters at the end, e.g., with parentheses in negative * numbers: ""(12)"" for -12. *
* *

Synchronization

* *

* Number formats are generally not synchronized. * It is recommended to create separate format instances for each thread. * If multiple threads access a format concurrently, it must be synchronized * externally. * * @implSpec The {@link #format(double, StringBuffer, FieldPosition)}, * {@link #format(long, StringBuffer, FieldPosition)} and * {@link #parse(String, ParsePosition)} methods may throw * {@code NullPointerException}, if any of their parameter is {@code null}. * The subclass may provide its own implementation and specification about * {@code NullPointerException}. * *

* The default implementation provides rounding modes defined * in {@link java.math.RoundingMode} for formatting numbers. It * uses the {@linkplain java.math.RoundingMode#HALF_EVEN * round half-even algorithm}. To change the rounding mode use * {@link #setRoundingMode(java.math.RoundingMode) setRoundingMode}. * The {@code NumberFormat} returned by the static factory methods is * configured to round floating point numbers using half-even * rounding (see {@link java.math.RoundingMode#HALF_EVEN * RoundingMode.HALF_EVEN}) for formatting. * * @see DecimalFormat * @see ChoiceFormat * @author Mark Davis * @author Helena Shih * @since 1.1 */ public abstract class NumberFormat extends Format { /** * Field constant used to construct a FieldPosition object. Signifies that * the position of the integer part of a formatted number should be returned. * @see java.text.FieldPosition */ public static final int INTEGER_FIELD = 0; /** * Field constant used to construct a FieldPosition object. Signifies that * the position of the fraction part of a formatted number should be returned. * @see java.text.FieldPosition */ public static final int FRACTION_FIELD = 1; /** * Sole constructor. (For invocation by subclass constructors, typically * implicit.) */ protected NumberFormat() { } /** * Formats a number and appends the resulting text to the given string * buffer. * The number can be of any subclass of {@link java.lang.Number}. *

* This implementation extracts the number's value using * {@link java.lang.Number#longValue()} for all integral type values that * can be converted to long without loss of information, * including BigInteger values with a * {@link java.math.BigInteger#bitLength() bit length} of less than 64, * and {@link java.lang.Number#doubleValue()} for all other types. It * then calls * {@link #format(long,java.lang.StringBuffer,java.text.FieldPosition)} * or {@link #format(double,java.lang.StringBuffer,java.text.FieldPosition)}. * This may result in loss of magnitude information and precision for * BigInteger and BigDecimal values. * @param number the number to format * @param toAppendTo the StringBuffer to which the formatted * text is to be appended * @param pos keeps track on the position of the field within the * returned string. For example, for formatting a number * {@code 1234567.89} in {@code Locale.US} locale, * if the given {@code fieldPosition} is * {@link NumberFormat#INTEGER_FIELD}, the begin index * and end index of {@code fieldPosition} will be set * to 0 and 9, respectively for the output string * {@code 1,234,567.89}. * @return the value passed in as toAppendTo * @exception IllegalArgumentException if number is * null or not an instance of Number. * @exception NullPointerException if toAppendTo or * pos is null * @exception ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.FieldPosition */ @Override public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) { if (number instanceof Long || number instanceof Integer || number instanceof Short || number instanceof Byte || number instanceof AtomicInteger || number instanceof AtomicLong || (number instanceof BigInteger && ((BigInteger)number).bitLength() < 64)) { return format(((Number)number).longValue(), toAppendTo, pos); } else if (number instanceof Number) { return format(((Number)number).doubleValue(), toAppendTo, pos); } else { throw new IllegalArgumentException(""Cannot format given Object as a Number""); } } /** * Parses text from a string to produce a Number. *

* The method attempts to parse text starting at the index given by * pos. * If parsing succeeds, then the index of pos is updated * to the index after the last character used (parsing does not necessarily * use all characters up to the end of the string), and the parsed * number is returned. The updated pos can be used to * indicate the starting point for the next call to this method. * If an error occurs, then the index of pos is not * changed, the error index of pos is set to the index of * the character where the error occurred, and null is returned. *

* See the {@link #parse(String, ParsePosition)} method for more information * on number parsing. * * @param [MASK] A String, part of which should be parsed. * @param pos A ParsePosition object with index and error * index information as described above. * @return A Number parsed from the string. In case of * error, returns null. * @throws NullPointerException if {@code [MASK] } or {@code pos} is null. */ @Override public final Object parseObject(String [MASK] , ParsePosition pos) { return parse( [MASK] , pos); } /** * Specialization of format. * * @param number the double number to format * @return the formatted String * @exception ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public final String format(double number) { // Android-removed: fast-path code. return format(number, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString(); } // Android-removed: fastFormat method. /** * Specialization of format. * * @param number the long number to format * @return the formatted String * @exception ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public final String format(long number) { return format(number, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString(); } /** * Specialization of format. * * @param number the double number to format * @param toAppendTo the StringBuffer to which the formatted text is to be * appended * @param pos keeps track on the position of the field within the * returned string. For example, for formatting a number * {@code 1234567.89} in {@code Locale.US} locale, * if the given {@code fieldPosition} is * {@link NumberFormat#INTEGER_FIELD}, the begin index * and end index of {@code fieldPosition} will be set * to 0 and 9, respectively for the output string * {@code 1,234,567.89}. * @return the formatted StringBuffer * @exception ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public abstract StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos); /** * Specialization of format. * * @param number the long number to format * @param toAppendTo the StringBuffer to which the formatted text is to be * appended * @param pos keeps track on the position of the field within the * returned string. For example, for formatting a number * {@code 123456789} in {@code Locale.US} locale, * if the given {@code fieldPosition} is * {@link NumberFormat#INTEGER_FIELD}, the begin index * and end index of {@code fieldPosition} will be set * to 0 and 11, respectively for the output string * {@code 123,456,789}. * @return the formatted StringBuffer * @exception ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public abstract StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos); /** * Returns a Long if possible (e.g., within the range [Long.MIN_VALUE, * Long.MAX_VALUE] and with no decimals), otherwise a Double. * If IntegerOnly is set, will stop at a decimal * point (or equivalent; e.g., for rational numbers ""1 2/3"", will stop * after the 1). * Does not throw an exception; if no object can be parsed, index is * unchanged! * * @param [MASK] the String to parse * @param parsePosition the parse position * @return the parsed value * @see java.text.NumberFormat#isParseIntegerOnly * @see java.text.Format#parseObject */ public abstract Number parse(String [MASK] , ParsePosition parsePosition); /** * Parses text from the beginning of the given string to produce a number. * The method may not use the entire text of the given string. *

* See the {@link #parse(String, ParsePosition)} method for more information * on number parsing. * * @param [MASK] A String whose beginning should be parsed. * @return A Number parsed from the string. * @exception ParseException if the beginning of the specified string * cannot be parsed. */ public Number parse(String [MASK] ) throws ParseException { ParsePosition parsePosition = new ParsePosition(0); Number result = parse( [MASK] , parsePosition); if (parsePosition.index == 0) { throw new ParseException(""Unparseable number: \"""" + [MASK] + ""\"""", parsePosition.errorIndex); } return result; } /** * Returns true if this format will parse numbers as integers only. * For example in the English locale, with ParseIntegerOnly true, the * string ""1234."" would be parsed as the integer value 1234 and parsing * would stop at the ""."" character. Of course, the exact format accepted * by the parse operation is locale dependent and determined by sub-classes * of NumberFormat. * * @return {@code true} if numbers should be parsed as integers only; * {@code false} otherwise */ public boolean isParseIntegerOnly() { return parseIntegerOnly; } /** * Sets whether or not numbers should be parsed as integers only. * * @param value {@code true} if numbers should be parsed as integers only; * {@code false} otherwise * @see #isParseIntegerOnly */ public void setParseIntegerOnly(boolean value) { parseIntegerOnly = value; } //============== Locale Stuff ===================== /** * Returns a general-purpose number format for the current default * {@link java.util.Locale.Category#FORMAT FORMAT} locale. * This is the same as calling * {@link #getNumberInstance() getNumberInstance()}. * * @return the {@code NumberFormat} instance for general-purpose number * formatting */ public static final NumberFormat getInstance() { return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); } /** * Returns a general-purpose number format for the specified locale. * This is the same as calling * {@link #getNumberInstance(java.util.Locale) getNumberInstance(inLocale)}. * * @param inLocale the desired locale * @return the {@code NumberFormat} instance for general-purpose number * formatting */ public static NumberFormat getInstance(Locale inLocale) { return getInstance(inLocale, NUMBERSTYLE); } /** * Returns a general-purpose number format for the current default * {@link java.util.Locale.Category#FORMAT FORMAT} locale. *

This is equivalent to calling * {@link #getNumberInstance(Locale) * getNumberInstance(Locale.getDefault(Locale.Category.FORMAT))}. * * @return the {@code NumberFormat} instance for general-purpose number * formatting * @see java.util.Locale#getDefault(java.util.Locale.Category) * @see java.util.Locale.Category#FORMAT */ public static final NumberFormat getNumberInstance() { return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); } /** * Returns a general-purpose number format for the specified locale. * * @param inLocale the desired locale * @return the {@code NumberFormat} instance for general-purpose number * formatting */ public static NumberFormat getNumberInstance(Locale inLocale) { return getInstance(inLocale, NUMBERSTYLE); } /** * Returns an integer number format for the current default * {@link java.util.Locale.Category#FORMAT FORMAT} locale. The * returned number format is configured to round floating point numbers * to the nearest integer using half-even rounding (see {@link * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, * and to parse only the integer part of an input string (see {@link * #isParseIntegerOnly isParseIntegerOnly}). *

This is equivalent to calling * {@link #getIntegerInstance(Locale) * getIntegerInstance(Locale.getDefault(Locale.Category.FORMAT))}. * * @see #getRoundingMode() * @see java.util.Locale#getDefault(java.util.Locale.Category) * @see java.util.Locale.Category#FORMAT * @return a number format for integer values * @since 1.4 */ public static final NumberFormat getIntegerInstance() { return getInstance(Locale.getDefault(Locale.Category.FORMAT), INTEGERSTYLE); } /** * Returns an integer number format for the specified locale. The * returned number format is configured to round floating point numbers * to the nearest integer using half-even rounding (see {@link * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, * and to parse only the integer part of an input string (see {@link * #isParseIntegerOnly isParseIntegerOnly}). * * @param inLocale the desired locale * @see #getRoundingMode() * @return a number format for integer values * @since 1.4 */ public static NumberFormat getIntegerInstance(Locale inLocale) { return getInstance(inLocale, INTEGERSTYLE); } /** * Returns a currency format for the current default * {@link java.util.Locale.Category#FORMAT FORMAT} locale. *

This is equivalent to calling * {@link #getCurrencyInstance(Locale) * getCurrencyInstance(Locale.getDefault(Locale.Category.FORMAT))}. * * @return the {@code NumberFormat} instance for currency formatting * @see java.util.Locale#getDefault(java.util.Locale.Category) * @see java.util.Locale.Category#FORMAT */ public static final NumberFormat getCurrencyInstance() { return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE); } /** * Returns a currency format for the specified locale. * * @param inLocale the desired locale * @return the {@code NumberFormat} instance for currency formatting */ public static NumberFormat getCurrencyInstance(Locale inLocale) { return getInstance(inLocale, CURRENCYSTYLE); } /** * Returns a percentage format for the current default * {@link java.util.Locale.Category#FORMAT FORMAT} locale. *

This is equivalent to calling * {@link #getPercentInstance(Locale) * getPercentInstance(Locale.getDefault(Locale.Category.FORMAT))}. * * @return the {@code NumberFormat} instance for percentage formatting * @see java.util.Locale#getDefault(java.util.Locale.Category) * @see java.util.Locale.Category#FORMAT */ public static final NumberFormat getPercentInstance() { return getInstance(Locale.getDefault(Locale.Category.FORMAT), PERCENTSTYLE); } /** * Returns a percentage format for the specified locale. * * @param inLocale the desired locale * @return the {@code NumberFormat} instance for percentage formatting */ public static NumberFormat getPercentInstance(Locale inLocale) { return getInstance(inLocale, PERCENTSTYLE); } // Android-removed: non-API methods getScientificInstance([Locale]). // Android-changed: Removed reference to NumberFormatProvider. /** * Returns an array of all locales for which the * get*Instance methods of this class can return * localized instances. * * @return An array of locales for which localized * NumberFormat instances are available. */ public static Locale[] getAvailableLocales() { // Android-changed: Removed used of NumberFormatProvider. Switched to use ICU. return ICU.getAvailableLocales(); } /** * Overrides hashCode. */ @Override public int hashCode() { return maximumIntegerDigits * 37 + maxFractionDigits; // just enough fields for a reasonable distribution } /** * Overrides equals. */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (getClass() != obj.getClass()) { return false; } NumberFormat other = (NumberFormat) obj; return (maximumIntegerDigits == other.maximumIntegerDigits && minimumIntegerDigits == other.minimumIntegerDigits && maximumFractionDigits == other.maximumFractionDigits && minimumFractionDigits == other.minimumFractionDigits && groupingUsed == other.groupingUsed && parseIntegerOnly == other.parseIntegerOnly); } /** * Overrides Cloneable. */ @Override public Object clone() { NumberFormat other = (NumberFormat) super.clone(); return other; } /** * Returns true if grouping is used in this format. For example, in the * English locale, with grouping on, the number 1234567 might be formatted * as ""1,234,567"". The grouping separator as well as the size of each group * is locale dependent and is determined by sub-classes of NumberFormat. * * @return {@code true} if grouping is used; * {@code false} otherwise * @see #setGroupingUsed */ public boolean isGroupingUsed() { return groupingUsed; } /** * Set whether or not grouping will be used in this format. * * @param newValue {@code true} if grouping is used; * {@code false} otherwise * @see #isGroupingUsed */ public void setGroupingUsed(boolean newValue) { groupingUsed = newValue; } /** * Returns the maximum number of digits allowed in the integer portion of a * number. * * @return the maximum number of digits * @see #setMaximumIntegerDigits */ public int getMaximumIntegerDigits() { return maximumIntegerDigits; } /** * Sets the maximum number of digits allowed in the integer portion of a * number. maximumIntegerDigits must be ≥ minimumIntegerDigits. If the * new value for maximumIntegerDigits is less than the current value * of minimumIntegerDigits, then minimumIntegerDigits will also be set to * the new value. * * @param newValue the maximum number of integer digits to be shown; if * less than zero, then zero is used. The concrete subclass may enforce an * upper limit to this value appropriate to the numeric type being formatted. * @see #getMaximumIntegerDigits */ public void setMaximumIntegerDigits(int newValue) { maximumIntegerDigits = Math.max(0,newValue); if (minimumIntegerDigits > maximumIntegerDigits) { minimumIntegerDigits = maximumIntegerDigits; } } /** * Returns the minimum number of digits allowed in the integer portion of a * number. * * @return the minimum number of digits * @see #setMinimumIntegerDigits */ public int getMinimumIntegerDigits() { return minimumIntegerDigits; } /** * Sets the minimum number of digits allowed in the integer portion of a * number. minimumIntegerDigits must be ≤ maximumIntegerDigits. If the * new value for minimumIntegerDigits exceeds the current value * of maximumIntegerDigits, then maximumIntegerDigits will also be set to * the new value * * @param newValue the minimum number of integer digits to be shown; if * less than zero, then zero is used. The concrete subclass may enforce an * upper limit to this value appropriate to the numeric type being formatted. * @see #getMinimumIntegerDigits */ public void setMinimumIntegerDigits(int newValue) { minimumIntegerDigits = Math.max(0,newValue); if (minimumIntegerDigits > maximumIntegerDigits) { maximumIntegerDigits = minimumIntegerDigits; } } /** * Returns the maximum number of digits allowed in the fraction portion of a * number. * * @return the maximum number of digits. * @see #setMaximumFractionDigits */ public int getMaximumFractionDigits() { return maximumFractionDigits; } /** * Sets the maximum number of digits allowed in the fraction portion of a * number. maximumFractionDigits must be ≥ minimumFractionDigits. If the * new value for maximumFractionDigits is less than the current value * of minimumFractionDigits, then minimumFractionDigits will also be set to * the new value. * * @param newValue the maximum number of fraction digits to be shown; if * less than zero, then zero is used. The concrete subclass may enforce an * upper limit to this value appropriate to the numeric type being formatted. * @see #getMaximumFractionDigits */ public void setMaximumFractionDigits(int newValue) { maximumFractionDigits = Math.max(0,newValue); if (maximumFractionDigits < minimumFractionDigits) { minimumFractionDigits = maximumFractionDigits; } } /** * Returns the minimum number of digits allowed in the fraction portion of a * number. * * @return the minimum number of digits * @see #setMinimumFractionDigits */ public int getMinimumFractionDigits() { return minimumFractionDigits; } /** * Sets the minimum number of digits allowed in the fraction portion of a * number. minimumFractionDigits must be ≤ maximumFractionDigits. If the * new value for minimumFractionDigits exceeds the current value * of maximumFractionDigits, then maximumIntegerDigits will also be set to * the new value * * @param newValue the minimum number of fraction digits to be shown; if * less than zero, then zero is used. The concrete subclass may enforce an * upper limit to this value appropriate to the numeric type being formatted. * @see #getMinimumFractionDigits */ public void setMinimumFractionDigits(int newValue) { minimumFractionDigits = Math.max(0,newValue); if (maximumFractionDigits < minimumFractionDigits) { maximumFractionDigits = minimumFractionDigits; } } /** * Gets the currency used by this number format when formatting * currency values. The initial value is derived in a locale dependent * way. The returned value may be null if no valid * currency could be determined and no currency has been set using * {@link #setCurrency(java.util.Currency) setCurrency}. *

* The default implementation throws * UnsupportedOperationException. * * @return the currency used by this number format, or null * @exception UnsupportedOperationException if the number format class * doesn't implement currency formatting * @since 1.4 */ public Currency getCurrency() { throw new UnsupportedOperationException(); } /** * Sets the currency used by this number format when formatting * currency values. This does not update the minimum or maximum * number of fraction digits used by the number format. *

* The default implementation throws * UnsupportedOperationException. * * @param currency the new currency to be used by this number format * @exception UnsupportedOperationException if the number format class * doesn't implement currency formatting * @exception NullPointerException if currency is null * @since 1.4 */ public void setCurrency(Currency currency) { throw new UnsupportedOperationException(); } /** * Gets the {@link java.math.RoundingMode} used in this NumberFormat. * The default implementation of this method in NumberFormat * always throws {@link java.lang.UnsupportedOperationException}. * Subclasses which handle different rounding modes should override * this method. * * @exception UnsupportedOperationException The default implementation * always throws this exception * @return The RoundingMode used for this NumberFormat. * @see #setRoundingMode(RoundingMode) * @since 1.6 */ public RoundingMode getRoundingMode() { throw new UnsupportedOperationException(); } /** * Sets the {@link java.math.RoundingMode} used in this NumberFormat. * The default implementation of this method in NumberFormat always * throws {@link java.lang.UnsupportedOperationException}. * Subclasses which handle different rounding modes should override * this method. * * @exception UnsupportedOperationException The default implementation * always throws this exception * @exception NullPointerException if roundingMode is null * @param roundingMode The RoundingMode to be used * @see #getRoundingMode() * @since 1.6 */ public void setRoundingMode(RoundingMode roundingMode) { throw new UnsupportedOperationException(); } // =======================privates=============================== private static NumberFormat getInstance(Locale desiredLocale, int choice) { // Check whether a provider can provide an implementation that's closer // to the requested locale than what the Java runtime itself can provide. /* J2ObjC: java.text.spi is not provided. LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(NumberFormatProvider.class); if (pool.hasProviders()) { NumberFormat providersInstance = pool.getLocalizedObject( NumberFormatGetter.INSTANCE, desiredLocale, choice); if (providersInstance != null) { return providersInstance; } }*/ /* try the cache first */ String[] numberPatterns = (String[])cachedLocaleData.get(desiredLocale); if (numberPatterns == null) { /* cache miss */ LocaleData data = LocaleData.get(desiredLocale); numberPatterns = new String[4]; numberPatterns[NUMBERSTYLE] = data.numberPattern; numberPatterns[CURRENCYSTYLE] = data.currencyPattern; numberPatterns[PERCENTSTYLE] = data.percentPattern; numberPatterns[INTEGERSTYLE] = data.integerPattern; /* update cache */ cachedLocaleData.put(desiredLocale, numberPatterns); } DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(desiredLocale); int entry = (choice == INTEGERSTYLE) ? NUMBERSTYLE : choice; DecimalFormat format = new DecimalFormat(numberPatterns[entry], symbols); if (choice == INTEGERSTYLE) { format.setMaximumFractionDigits(0); format.setDecimalSeparatorAlwaysShown(false); format.setParseIntegerOnly(true); } else if (choice == CURRENCYSTYLE) { adjustForCurrencyDefaultFractionDigits(format, symbols); } // END Android-changed: Removed use of NumberFormatProvider. Switched to use ICU. return format; } // J2ObjC: This method exists in DecimalFormat.java in android [MASK] . // It exists in sun.util.locale.provider.NumberFormatProviderImpl in openjdk [MASK] . /** * Adjusts the minimum and maximum fraction digits to values that * are reasonable for the currency's default fraction digits. */ private static void adjustForCurrencyDefaultFractionDigits( DecimalFormat format, DecimalFormatSymbols symbols) { Currency currency = symbols.getCurrency(); if (currency == null) { try { currency = Currency.getInstance(symbols.getInternationalCurrencySymbol()); } catch (IllegalArgumentException e) { } } if (currency != null) { int digits = currency.getDefaultFractionDigits(); if (digits != -1) { int oldMinDigits = format.getMinimumFractionDigits(); // Common patterns are ""#.##"", ""#.00"", ""#"". // Try to adjust all of them in a reasonable way. if (oldMinDigits == format.getMaximumFractionDigits()) { format.setMinimumFractionDigits(digits); format.setMaximumFractionDigits(digits); } else { format.setMinimumFractionDigits(Math.min(digits, oldMinDigits)); format.setMaximumFractionDigits(digits); } } } } /** * First, read in the default serializable data. * * Then, if serialVersionOnStream is less than 1, indicating that * the stream was written by JDK 1.1, * set the int fields such as maximumIntegerDigits * to be equal to the byte fields such as maxIntegerDigits, * since the int fields were not present in JDK 1.1. * Finally, set serialVersionOnStream back to the maximum allowed value so that * default serialization will work properly if this object is streamed out again. * *

If minimumIntegerDigits is greater than * maximumIntegerDigits or minimumFractionDigits * is greater than maximumFractionDigits, then the stream data * is invalid and this method throws an InvalidObjectException. * In addition, if any of these values is negative, then this method throws * an InvalidObjectException. * * @since 1.2 */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); if (serialVersionOnStream < 1) { // Didn't have additional int fields, reassign to use them. maximumIntegerDigits = maxIntegerDigits; minimumIntegerDigits = minIntegerDigits; maximumFractionDigits = maxFractionDigits; minimumFractionDigits = minFractionDigits; } if (minimumIntegerDigits > maximumIntegerDigits || minimumFractionDigits > maximumFractionDigits || minimumIntegerDigits < 0 || minimumFractionDigits < 0) { throw new InvalidObjectException(""Digit count range invalid""); } serialVersionOnStream = currentSerialVersion; } /** * Write out the default serializable data, after first setting * the byte fields such as maxIntegerDigits to be * equal to the int fields such as maximumIntegerDigits * (or to Byte.MAX_VALUE, whichever is smaller), for compatibility * with the JDK 1.1 version of the stream format. * * @since 1.2 */ private void writeObject(ObjectOutputStream stream) throws IOException { maxIntegerDigits = (maximumIntegerDigits > Byte.MAX_VALUE) ? Byte.MAX_VALUE : (byte)maximumIntegerDigits; minIntegerDigits = (minimumIntegerDigits > Byte.MAX_VALUE) ? Byte.MAX_VALUE : (byte)minimumIntegerDigits; maxFractionDigits = (maximumFractionDigits > Byte.MAX_VALUE) ? Byte.MAX_VALUE : (byte)maximumFractionDigits; minFractionDigits = (minimumFractionDigits > Byte.MAX_VALUE) ? Byte.MAX_VALUE : (byte)minimumFractionDigits; stream.defaultWriteObject(); } /** * Cache to hold the NumberPatterns of a Locale. */ private static final Hashtable cachedLocaleData = new Hashtable(3); // Constants used by factory methods to specify a style of format. private static final int NUMBERSTYLE = 0; private static final int CURRENCYSTYLE = 1; private static final int PERCENTSTYLE = 2; // Android-changed: changed: removed SCIENTIFICSTYLE and pull down INTEGERSTYLE value. //private static final int SCIENTIFICSTYLE = 3; private static final int INTEGERSTYLE = 3; /** * True if the grouping (i.e. thousands) separator is used when * formatting and parsing numbers. * * @serial * @see #isGroupingUsed */ private boolean groupingUsed = true; /** * The maximum number of digits allowed in the integer portion of a * number. maxIntegerDigits must be greater than or equal to * minIntegerDigits. *

* Note: This field exists only for serialization * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new * int field maximumIntegerDigits is used instead. * When writing to a stream, maxIntegerDigits is set to * maximumIntegerDigits or Byte.MAX_VALUE, * whichever is smaller. When reading from a stream, this field is used * only if serialVersionOnStream is less than 1. * * @serial * @see #getMaximumIntegerDigits */ private byte maxIntegerDigits = 40; /** * The minimum number of digits allowed in the integer portion of a * number. minimumIntegerDigits must be less than or equal to * maximumIntegerDigits. *

* Note: This field exists only for serialization * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new * int field minimumIntegerDigits is used instead. * When writing to a stream, minIntegerDigits is set to * minimumIntegerDigits or Byte.MAX_VALUE, * whichever is smaller. When reading from a stream, this field is used * only if serialVersionOnStream is less than 1. * * @serial * @see #getMinimumIntegerDigits */ private byte minIntegerDigits = 1; /** * The maximum number of digits allowed in the fractional portion of a * number. maximumFractionDigits must be greater than or equal to * minimumFractionDigits. *

* Note: This field exists only for serialization * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new * int field maximumFractionDigits is used instead. * When writing to a stream, maxFractionDigits is set to * maximumFractionDigits or Byte.MAX_VALUE, * whichever is smaller. When reading from a stream, this field is used * only if serialVersionOnStream is less than 1. * * @serial * @see #getMaximumFractionDigits */ private byte maxFractionDigits = 3; // invariant, >= minFractionDigits /** * The minimum number of digits allowed in the fractional portion of a * number. minimumFractionDigits must be less than or equal to * maximumFractionDigits. *

* Note: This field exists only for serialization * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new * int field minimumFractionDigits is used instead. * When writing to a stream, minFractionDigits is set to * minimumFractionDigits or Byte.MAX_VALUE, * whichever is smaller. When reading from a stream, this field is used * only if serialVersionOnStream is less than 1. * * @serial * @see #getMinimumFractionDigits */ private byte minFractionDigits = 0; /** * True if this format will parse numbers as integers only. * * @serial * @see #isParseIntegerOnly */ private boolean parseIntegerOnly = false; // new fields for 1.2. byte is too small for integer digits. /** * The maximum number of digits allowed in the integer portion of a * number. maximumIntegerDigits must be greater than or equal to * minimumIntegerDigits. * * @serial * @since 1.2 * @see #getMaximumIntegerDigits */ private int maximumIntegerDigits = 40; /** * The minimum number of digits allowed in the integer portion of a * number. minimumIntegerDigits must be less than or equal to * maximumIntegerDigits. * * @serial * @since 1.2 * @see #getMinimumIntegerDigits */ private int minimumIntegerDigits = 1; /** * The maximum number of digits allowed in the fractional portion of a * number. maximumFractionDigits must be greater than or equal to * minimumFractionDigits. * * @serial * @since 1.2 * @see #getMaximumFractionDigits */ private int maximumFractionDigits = 3; // invariant, >= minFractionDigits /** * The minimum number of digits allowed in the fractional portion of a * number. minimumFractionDigits must be less than or equal to * maximumFractionDigits. * * @serial * @since 1.2 * @see #getMinimumFractionDigits */ private int minimumFractionDigits = 0; static final int currentSerialVersion = 1; /** * Describes the version of NumberFormat present on the stream. * Possible values are: *

    *
  • 0 (or uninitialized): the JDK 1.1 version of the stream format. * In this version, the int fields such as * maximumIntegerDigits were not present, and the byte * fields such as maxIntegerDigits are used instead. * *
  • 1: the 1.2 version of the stream format. The values of the * byte fields such as maxIntegerDigits are ignored, * and the int fields such as maximumIntegerDigits * are used instead. *
* When streaming out a NumberFormat, the most recent format * (corresponding to the highest allowable serialVersionOnStream) * is always written. * * @serial * @since 1.2 */ private int serialVersionOnStream = currentSerialVersion; // Removed ""implements Cloneable"" clause. Needs to update serialization // ID for backward compatibility. static final long serialVersionUID = -2308460125733713944L; // // class for AttributedCharacterIterator attributes // /** * Defines constants that are used as attribute keys in the * AttributedCharacterIterator returned * from NumberFormat.formatToCharacterIterator and as * field identifiers in FieldPosition. * * @since 1.4 */ public static class Field extends Format.Field { // Proclaim serial compatibility with 1.4 FCS private static final long serialVersionUID = 7494728892700160890L; // table of all instances in this class, used by readResolve private static final Map instanceMap = new HashMap(11); /** * Creates a Field instance with the specified * name. * * @param name Name of the attribute */ protected Field(String name) { super(name); if (this.getClass() == NumberFormat.Field.class) { instanceMap.put(name, this); } } /** * Resolves instances being deserialized to the predefined constants. * * @throws InvalidObjectException if the constant could not be resolved. * @return resolved NumberFormat.Field constant */ @Override protected Object readResolve() throws InvalidObjectException { if (this.getClass() != NumberFormat.Field.class) { throw new InvalidObjectException(""subclass didn't correctly implement readResolve""); } Object instance = instanceMap.get(getName()); if (instance != null) { return instance; } else { throw new InvalidObjectException(""unknown attribute name""); } } /** * Constant identifying the integer field. */ public static final Field INTEGER = new Field(""integer""); /** * Constant identifying the fraction field. */ public static final Field FRACTION = new Field(""fraction""); /** * Constant identifying the exponent field. */ public static final Field EXPONENT = new Field(""exponent""); /** * Constant identifying the decimal separator field. */ public static final Field DECIMAL_SEPARATOR = new Field(""decimal separator""); /** * Constant identifying the sign field. */ public static final Field SIGN = new Field(""sign""); /** * Constant identifying the grouping separator field. */ public static final Field GROUPING_SEPARATOR = new Field(""grouping separator""); /** * Constant identifying the exponent symbol field. */ public static final Field EXPONENT_SYMBOL = new Field(""exponent symbol""); /** * Constant identifying the percent field. */ public static final Field PERCENT = new Field(""percent""); /** * Constant identifying the permille field. */ public static final Field PERMILLE = new Field(""per mille""); /** * Constant identifying the currency field. */ public static final Field CURRENCY = new Field(""currency""); /** * Constant identifying the exponent sign field. */ public static final Field EXPONENT_SIGN = new Field(""exponent sign""); } /** * Obtains a NumberFormat instance from a NumberFormatProvider implementation. * J2ObjC: java.text.spi is not provided. private static class NumberFormatGetter implements LocaleServiceProviderPool.LocalizedObjectGetter { private static final NumberFormatGetter INSTANCE = new NumberFormatGetter(); public NumberFormat getObject(NumberFormatProvider numberFormatProvider, Locale locale, String key, Object... params) { assert params.length == 1; int choice = (Integer)params[0]; switch (choice) { case NUMBERSTYLE: return numberFormatProvider.getNumberInstance(locale); case PERCENTSTYLE: return numberFormatProvider.getPercentInstance(locale); case CURRENCYSTYLE: return numberFormatProvider.getCurrencyInstance(locale); case INTEGERSTYLE: return numberFormatProvider.getIntegerInstance(locale); default: assert false : choice; } return null; } }*/ } ","source " "/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.transformer; import static java.lang.Math.pow; /** * Image comparison tool that calculates the Mean Structural Similarity (MSSIM) of two images, * developed by Wang, Bovik, Sheikh, and Simoncelli. * *

MSSIM divides the image into windows, calculates SSIM of each, then returns the average. * *

See the SSIM paper. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public final class MssimCalculator { // Referred to as 'L' in the SSIM paper, this constant defines the maximum pixel values. The // range of pixel values is 0 to 255 (8 bit unsigned range). private static final int PIXEL_MAX_VALUE = 255; // K1 and K2, as defined in the SSIM paper. private static final double K1 = 0.01; private static final double K2 = 0.03; // C1 and C2 stabilize the SSIM value when either (referenceMean^2 + [MASK] ^2) or // (referenceVariance + distortedVariance) is close to 0. See the SSIM formula in // `getWindowSsim` for how these values impact each other in the calculation. private static final double C1 = pow(PIXEL_MAX_VALUE * K1, 2); private static final double C2 = pow(PIXEL_MAX_VALUE * K2, 2); private static final int WINDOW_SIZE = 8; private MssimCalculator() {} /** * Calculates the Mean Structural Similarity (MSSIM) between two images with window skipping. * * @see #calculate(byte[], byte[], int, int, boolean) */ public static double calculate( byte[] referenceBuffer, byte[] distortedBuffer, int width, int height) { return calculate( referenceBuffer, distortedBuffer, width, height, /* enableWindowSkipping= */ true); } /** * Calculates the Mean Structural Similarity (MSSIM) between two images. * *

The images are split into a grid of windows. For each window, the structural similarity * (SSIM) is calculated. The MSSIM returned from this method is the mean of these SSIM values. If * window skipping is enabled, only every other row and column are considered, thereby only one in * four windows are evaluated. * * @param referenceBuffer The luma channel (Y) buffer of the reference image. * @param distortedBuffer The luma channel (Y) buffer of the distorted image. * @param width The image width in pixels. * @param height The image height in pixels. * @param enableWindowSkipping Whether to skip every other row and column when evaluating windows * for SSIM calculation. * @return The MSSIM score between the input images. */ public static double calculate( byte[] referenceBuffer, byte[] distortedBuffer, int width, int height, boolean enableWindowSkipping) { double totalSsim = 0; int windowsCount = 0; int dimensionIncrement = WINDOW_SIZE * (enableWindowSkipping ? 2 : 1); for (int currentWindowY = 0; currentWindowY < height; currentWindowY += dimensionIncrement) { int windowHeight = computeWindowSize(currentWindowY, height); for (int currentWindowX = 0; currentWindowX < width; currentWindowX += dimensionIncrement) { windowsCount++; int windowWidth = computeWindowSize(currentWindowX, width); int bufferIndexOffset = get1dIndex(currentWindowX, currentWindowY, /* stride= */ width, /* offset= */ 0); double referenceMean = getMean( referenceBuffer, bufferIndexOffset, /* stride= */ width, windowWidth, windowHeight); double [MASK] = getMean( distortedBuffer, bufferIndexOffset, /* stride= */ width, windowWidth, windowHeight); double[] variances = getVariancesAndCovariance( referenceBuffer, distortedBuffer, referenceMean, [MASK] , bufferIndexOffset, /* stride= */ width, windowWidth, windowHeight); double referenceVariance = variances[0]; double distortedVariance = variances[1]; double referenceDistortedCovariance = variances[2]; totalSsim += getWindowSsim( referenceMean, [MASK] , referenceVariance, distortedVariance, referenceDistortedCovariance); } } if (windowsCount == 0) { return 1.0d; } return totalSsim / windowsCount; } /** * Returns the window size at the provided start coordinate, uses {@link #WINDOW_SIZE} if there is * enough space, otherwise the number of pixels between {@code start} and {@code dimension}. */ private static int computeWindowSize(int start, int dimension) { if (start + WINDOW_SIZE <= dimension) { return WINDOW_SIZE; } return dimension - start; } /** Returns the SSIM of a window. */ private static double getWindowSsim( double referenceMean, double [MASK] , double referenceVariance, double distortedVariance, double referenceDistortedCovariance) { // Uses equation 13 on page 6 from the linked paper. double numerator = (((2 * referenceMean * [MASK] ) + C1) * ((2 * referenceDistortedCovariance) + C2)); double denominator = ((referenceMean * referenceMean) + ( [MASK] * [MASK] ) + C1) * (referenceVariance + distortedVariance + C2); return numerator / denominator; } /** Returns the mean of the pixels in the window. */ private static double getMean( byte[] pixelBuffer, int bufferIndexOffset, int stride, int windowWidth, int windowHeight) { double total = 0; for (int y = 0; y < windowHeight; y++) { for (int x = 0; x < windowWidth; x++) { total += pixelBuffer[get1dIndex(x, y, stride, bufferIndexOffset)] & 0xFF; } } return total / (windowWidth * windowHeight); } /** Calculates the variances and covariance of the pixels in the window for both buffers. */ private static double[] getVariancesAndCovariance( byte[] referenceBuffer, byte[] distortedBuffer, double referenceMean, double [MASK] , int bufferIndexOffset, int stride, int windowWidth, int windowHeight) { double referenceVariance = 0; double distortedVariance = 0; double referenceDistortedCovariance = 0; for (int y = 0; y < windowHeight; y++) { for (int x = 0; x < windowWidth; x++) { int index = get1dIndex(x, y, stride, bufferIndexOffset); double referencePixelDeviation = (referenceBuffer[index] & 0xFF) - referenceMean; double distortedPixelDeviation = (distortedBuffer[index] & 0xFF) - [MASK] ; referenceVariance += referencePixelDeviation * referencePixelDeviation; distortedVariance += distortedPixelDeviation * distortedPixelDeviation; referenceDistortedCovariance += referencePixelDeviation * distortedPixelDeviation; } } int normalizationFactor = windowWidth * windowHeight - 1; return new double[] { referenceVariance / normalizationFactor, distortedVariance / normalizationFactor, referenceDistortedCovariance / normalizationFactor }; } /** * Translates a 2D coordinate into an 1D index, based on the stride of the 2D space. * * @param x The width component of coordinate. * @param y The height component of coordinate. * @param stride The width of the 2D space. * @param offset An offset to apply. * @return The 1D index. */ private static int get1dIndex(int x, int y, int stride, int offset) { return x + (y * stride) + offset; } } ","distortedMean " "/* Copyright 2010, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS"" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.refine.exporters; import com.google.refine.ProjectManager; import com.google.refine.ProjectManagerStub; import com.google.refine.ProjectMetadata; import com.google.refine.RefineTest; import com.google.refine.browsing.Engine; import com.google.refine.browsing.Engine.Mode; import com.google.refine.model.Cell; import com.google.refine.model.Column; import com.google.refine.model.ModelException; import com.google.refine.model.Project; import com.google.refine.model.Row; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.odftoolkit.odfdom.doc.OdfDocument; import org.odftoolkit.odfdom.doc.table.OdfTable; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.OffsetDateTime; import java.util.List; import java.util.Properties; import static org.mockito.Mockito.mock; public class OdsExporterTests extends RefineTest { private static final String TEST_PROJECT_NAME = ""ods exporter test project""; @Override @BeforeTest public void init() { logger = LoggerFactory.getLogger(this.getClass()); } // dependencies ByteArrayOutputStream stream; ProjectMetadata projectMetadata; Project project; Engine engine; Properties options; // System Under Test StreamExporter SUT; @BeforeMethod public void SetUp() { SUT = new OdsExporter(); stream = new ByteArrayOutputStream(); ProjectManager.singleton = new ProjectManagerStub(); projectMetadata = new ProjectMetadata(); project = new Project(); projectMetadata.setName(TEST_PROJECT_NAME); ProjectManager.singleton.registerProject(project, projectMetadata); engine = new Engine(project); options = mock(Properties.class); } @AfterMethod public void TearDown() { SUT = null; stream = null; ProjectManager.singleton.deleteProject(project.id); project = null; engine = null; options = null; } @Test public void getContentType() { Assert.assertEquals(SUT.getContentType(), ""application/vnd.oasis.opendocument.spreadsheet""); } @Test public void exportSimpleOds() throws IOException { CreateGrid(2, 2); try { SUT.export(project, options, engine, stream); } catch (IOException e) { Assert.fail(); } try { OdfDocument odfDoc = OdfDocument.loadDocument(new ByteArrayInputStream(stream.toByteArray())); List tables = odfDoc.getTableList(); Assert.assertEquals(tables.size(), 1); // we deleted the first sheet generated by default OdfTable odfTab = tables.get(0); Assert.assertEquals(odfTab.getTableName(), ""ods exporter test project""); Assert.assertEquals(odfTab.getRowCount(), 3); // first row is header Assert.assertEquals(odfTab.getRowByIndex(1).getCellByIndex(0).getStringValue(), ""row0cell0""); } catch (Exception e) { Assert.fail(); } } protected void CreateColumns(int [MASK] ) { for (int i = 0; i < [MASK] ; i++) { try { project.columnModel.addColumn(i, new Column(i, ""column"" + i), true); } catch (ModelException e1) { Assert.fail(""Could not create column""); } } } protected void CreateGrid(int noOfRows, int [MASK] ) { CreateColumns( [MASK] ); for (int i = 0; i < noOfRows; i++) { Row row = new Row( [MASK] ); for (int j = 0; j < [MASK] ; j++) { row.cells.add(new Cell(""row"" + i + ""cell"" + j, null)); } project.rows.add(row); } } private void createDateGrid(int noOfRows, int [MASK] , OffsetDateTime now) { CreateColumns( [MASK] ); for (int i = 0; i < noOfRows; i++) { Row row = new Row( [MASK] ); for (int j = 0; j < [MASK] ; j++) { row.cells.add(new Cell(now, null)); } project.rows.add(row); } } } ","noOfColumns " "// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.analysis.config; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Iterables; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext; import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; import com.google.devtools.build.lib.skyframe.serialization.SerializationContext; import com.google.devtools.build.lib.skyframe.serialization.SerializationException; import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.util.OrderedSetMultimap; import com.google.devtools.common.options.OptionDefinition; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.OptionsParsingResult; import com.google.devtools.common.options.OptionsProvider; import com.google.devtools.common.options.ParsedOptionDescription; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.CodedInputStream; import com.google.protobuf.CodedOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import javax.annotation.Nullable; /** Stores the command-line options from a set of configuration fragments. */ // TODO(janakr): If overhead of FragmentOptions class names is too high, add constructor that just // takes fragments and gets names from them. public final class BuildOptions implements Cloneable { @SerializationConstant static final Comparator> LEXICAL_FRAGMENT_OPTIONS_COMPARATOR = Comparator.comparing(Class::getName); public static Map labelizeStarlarkOptions(Map starlarkOptions) { return starlarkOptions.entrySet().stream() .collect( Collectors.toMap(e -> Label.parseCanonicalUnchecked(e.getKey()), Map.Entry::getValue)); } public static BuildOptions getDefaultBuildOptionsForFragments( Iterable> fragmentClasses) { try { return BuildOptions.of(fragmentClasses); } catch (OptionsParsingException e) { throw new IllegalArgumentException(""Failed to parse empty options"", e); } } /** Creates a new BuildOptions instance for an exec configuration. */ public BuildOptions createExecOptions() { Builder builder = builder(); for (FragmentOptions options : fragmentOptionsMap.values()) { builder.addFragmentOptions(options.getExec()); } return builder.addStarlarkOptions(starlarkOptionsMap).build(); } /** * Creates a BuildOptions class by taking the option values from an options provider (eg. an * OptionsParser). */ public static BuildOptions of( Iterable> optionsList, OptionsProvider provider) { Builder builder = builder(); for (Class optionsClass : optionsList) { builder.addFragmentOptions(provider.getOptions(optionsClass)); } return builder .addStarlarkOptions(labelizeStarlarkOptions(provider.getStarlarkOptions())) .build(); } /** * Creates a BuildOptions class by taking the option values from command-line arguments. Returns a * BuildOptions class that only has native options. */ @VisibleForTesting public static BuildOptions of( Iterable> optionsList, String... args) throws OptionsParsingException { Builder builder = builder(); OptionsParser parser = OptionsParser.builder().optionsClasses(optionsList).build(); parser.parse(args); for (Class optionsClass : optionsList) { builder.addFragmentOptions(parser.getOptions(optionsClass)); } return builder.build(); } /* * Returns a BuildOptions class that only has Starlark options. */ @VisibleForTesting public static BuildOptions of(Map starlarkOptions) { return builder().addStarlarkOptions(starlarkOptions).build(); } /** Returns the actual instance of a FragmentOptions class. */ public T get(Class optionsClass) { FragmentOptions options = fragmentOptionsMap.get(optionsClass); checkNotNull(options, ""fragment options unavailable: %s"", optionsClass); return optionsClass.cast(options); } /** Returns true if these options contain the given {@link FragmentOptions}. */ public boolean contains(Class optionsClass) { return fragmentOptionsMap.containsKey(optionsClass); } /** * Are these options ""empty"", meaning they contain no meaningful configuration information? * *

See {@link com.google.devtools.build.lib.analysis.config.transitions.NoConfigTransition}. */ public boolean hasNoConfig() { // Ideally the implementation is fragmentOptionsMap.isEmpty() && starlarkOptionsMap.isEmpty(). // See NoConfigTransition for why CoreOptions stays included. return fragmentOptionsMap.size() == 1 && Iterables.getOnlyElement(fragmentOptionsMap.values()) .getClass() .getSimpleName() .equals(""CoreOptions"") && starlarkOptionsMap.isEmpty(); } /** Returns a hex digest string uniquely identifying the build options. */ public String checksum() { if (checksum == null) { synchronized (this) { if (checksum == null) { if (fragmentOptionsMap.isEmpty() && starlarkOptionsMap.isEmpty()) { checksum = ""0"".repeat(64); // Make empty build options easy to distinguish. } else { Fingerprint fingerprint = new Fingerprint(); for (FragmentOptions options : fragmentOptionsMap.values()) { fingerprint.addString(options.cacheKey()); } fingerprint.addString(OptionsBase.mapToCacheKey(starlarkOptionsMap)); checksum = fingerprint.hexDigestAndReset(); } } } } return checksum; } /** * Returns a user-friendly configuration identifier as a prefix of fullId. * *

This eliminates having to manipulate long full hashes, just like Git short commit hashes. */ public String shortId() { // Inherit Git's default commit hash prefix length. It's a principled choice with similar usage // patterns. cquery, which uses this, has access to every configuration in the build. If it // turns out this setting produces ambiguous prefixes, we could always compare configurations // to find the actual minimal unambiguous length. return checksum() == null ? ""null"" : checksum().substring(0, 7); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add(""checksum"", checksum()) .add(""fragmentOptions"", fragmentOptionsMap.values()) .add(""starlarkOptions"", starlarkOptionsMap) .toString(); } /** Returns the options contained in this collection, sorted by {@link FragmentOptions} name. */ public ImmutableCollection getNativeOptions() { return fragmentOptionsMap.values(); } /** * Returns the set of fragment classes contained in these options, sorted by {@link * FragmentOptions} name. */ public ImmutableSet> getFragmentClasses() { return fragmentOptionsMap.keySet(); } /** Starlark options, sorted lexicographically by name. */ public ImmutableMap getStarlarkOptions() { return starlarkOptionsMap; } /** * Creates a copy of the BuildOptions object that contains copies of the FragmentOptions and * Starlark options. */ @Override public BuildOptions clone() { ImmutableMap.Builder, FragmentOptions> nativeOptionsBuilder = ImmutableMap.builderWithExpectedSize(fragmentOptionsMap.size()); for (Map.Entry, FragmentOptions> entry : fragmentOptionsMap.entrySet()) { nativeOptionsBuilder.put(entry.getKey(), entry.getValue().clone()); } return new BuildOptions( nativeOptionsBuilder.buildOrThrow(), ImmutableMap.copyOf(starlarkOptionsMap)); } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof BuildOptions)) { return false; } return checksum().equals(((BuildOptions) other).checksum()); } @Override public int hashCode() { return 31 + checksum().hashCode(); } /** Maps options class definitions to FragmentOptions objects. */ private final ImmutableMap, FragmentOptions> fragmentOptionsMap; /** Maps Starlark options names to Starlark options values. */ private final ImmutableMap starlarkOptionsMap; // Lazily initialized both for performance and correctness - BuildOptions instances may be mutated // after construction but before consumption. Access via checksum() to ensure initialization. This // field is volatile as per https://errorprone.info/bugpattern/DoubleCheckedLocking, which // encourages using volatile even for immutable objects. @Nullable private volatile String checksum = null; private BuildOptions( ImmutableMap, FragmentOptions> fragmentOptionsMap, ImmutableMap starlarkOptionsMap) { this.fragmentOptionsMap = fragmentOptionsMap; this.starlarkOptionsMap = starlarkOptionsMap; } /** * Applies any options set in the parsing result on top of these options, returning the resulting * build options. * *

To preserve fragment trimming, this method will not expand the set of included native * fragments. If the parsing result contains native options whose owning fragment is not part of * these options they will be ignored (i.e. not set on the resulting options). Starlark options * are not affected by this restriction. * * @param parsingResult any options that are being modified * @return the new options after applying the parsing result to the original options */ public BuildOptions applyParsingResult(OptionsParsingResult parsingResult) { Map, FragmentOptions> modifiedFragments = toModifiedFragments(parsingResult); BuildOptions.Builder builder = builder(); for (Map.Entry, FragmentOptions> classAndFragment : fragmentOptionsMap.entrySet()) { Class fragmentClass = classAndFragment.getKey(); if (modifiedFragments.containsKey(fragmentClass)) { builder.addFragmentOptions(modifiedFragments.get(fragmentClass)); } else { builder.addFragmentOptions(classAndFragment.getValue()); } } Map starlarkOptions = new HashMap<>(starlarkOptionsMap); Map parsedStarlarkOptions = labelizeStarlarkOptions(parsingResult.getStarlarkOptions()); for (Map.Entry starlarkOption : parsedStarlarkOptions.entrySet()) { starlarkOptions.put(starlarkOption.getKey(), starlarkOption.getValue()); } builder.addStarlarkOptions(starlarkOptions); return builder.build(); } private Map, FragmentOptions> toModifiedFragments( OptionsParsingResult parsingResult) { Map, FragmentOptions> replacedOptions = new HashMap<>(); for (ParsedOptionDescription parsedOption : parsingResult.asListOfExplicitOptions()) { OptionDefinition optionDefinition = parsedOption.getOptionDefinition(); // All options obtained from an options parser are guaranteed to have been defined in an // FragmentOptions class. @SuppressWarnings(""unchecked"") Class [MASK] = (Class) optionDefinition.getField().getDeclaringClass(); FragmentOptions originalFragment = fragmentOptionsMap.get( [MASK] ); if (originalFragment == null) { // Preserve trimming by ignoring fragments not present in the original options. continue; } FragmentOptions newOptions = replacedOptions.computeIfAbsent( [MASK] , unused -> originalFragment.clone()); try { Object value = parsingResult.getOptionValueDescription(optionDefinition.getOptionName()).getValue(); optionDefinition.getField().set(newOptions, value); } catch (IllegalAccessException e) { throw new IllegalStateException(""Couldn't set "" + optionDefinition.getField(), e); } } return replacedOptions; } /** * Returns true if the passed parsing result's options have the same value as these options. * *

If a native parsed option is passed whose fragment has been trimmed in these options it is * considered to match. * *

If no options are present in the parsing result or all options in the parsing result have * been trimmed the result is considered not to match. This is because otherwise the parsing * result would match any options in a similar trimmed state, regardless of contents. * * @param parsingResult parsing result to be compared to these options * @return true if all non-trimmed values match * @throws OptionsParsingException if options cannot be parsed */ public boolean matches(OptionsParsingResult parsingResult) throws OptionsParsingException { Set ignoredDefinitions = new HashSet<>(); for (ParsedOptionDescription parsedOption : parsingResult.asListOfExplicitOptions()) { OptionDefinition optionDefinition = parsedOption.getOptionDefinition(); // All options obtained from an options parser are guaranteed to have been defined in an // FragmentOptions class. @SuppressWarnings(""unchecked"") Class fragmentClass = (Class) optionDefinition.getField().getDeclaringClass(); FragmentOptions originalFragment = fragmentOptionsMap.get(fragmentClass); if (originalFragment == null) { // Ignore flags set in trimmed fragments. ignoredDefinitions.add(optionDefinition); continue; } Object originalValue = originalFragment.asMap().get(optionDefinition.getOptionName()); if (!Objects.equals(originalValue, parsedOption.getConvertedValue())) { return false; } } Map starlarkOptions = labelizeStarlarkOptions(parsingResult.getStarlarkOptions()); MapDifference starlarkDifference = Maps.difference(starlarkOptionsMap, starlarkOptions); if (starlarkDifference.entriesInCommon().size() < starlarkOptions.size()) { return false; } if (ignoredDefinitions.size() == parsingResult.asListOfExplicitOptions().size() && starlarkOptions.isEmpty()) { // Zero options were compared, either because none were passed or because all of them were // trimmed. return false; } return true; } /** Creates a builder object for BuildOptions */ public static Builder builder() { return new Builder(); } /** Creates a builder operating on a clone of this BuildOptions. */ public Builder toBuilder() { return builder().merge(clone()); } /** Builder class for BuildOptions. */ public static class Builder { /** * Merges the given BuildOptions into this builder, overriding any previous instances of * Starlark options or FragmentOptions subclasses found in the new BuildOptions. */ @CanIgnoreReturnValue public Builder merge(BuildOptions options) { for (FragmentOptions fragment : options.getNativeOptions()) { this.addFragmentOptions(fragment); } this.addStarlarkOptions(options.getStarlarkOptions()); return this; } /** * Adds a new {@link FragmentOptions} instance to the builder. * *

Overrides previous instances of the exact same subclass of {@code FragmentOptions}. * *

The options get preprocessed with {@link FragmentOptions#getNormalized}. */ @CanIgnoreReturnValue public Builder addFragmentOptions(T options) { fragmentOptions.put(options.getClass(), options.getNormalized()); return this; } /** * Adds multiple Starlark options to the builder. Overrides previous instances of the same key. */ @CanIgnoreReturnValue public Builder addStarlarkOptions(Map options) { starlarkOptions.putAll(options); return this; } /** Adds a Starlark option to the builder. Overrides previous instances of the same key. */ @CanIgnoreReturnValue public Builder addStarlarkOption(Label key, Object value) { starlarkOptions.put(key, value); return this; } /** Removes the value for the {@link FragmentOptions} with the given FragmentOptions class. */ @CanIgnoreReturnValue public Builder removeFragmentOptions(Class key) { fragmentOptions.remove(key); return this; } /** Removes the value for the Starlark option with the given key. */ @CanIgnoreReturnValue public Builder removeStarlarkOption(Label key) { starlarkOptions.remove(key); return this; } public BuildOptions build() { return new BuildOptions( ImmutableSortedMap.copyOf(fragmentOptions, LEXICAL_FRAGMENT_OPTIONS_COMPARATOR), ImmutableSortedMap.copyOf(starlarkOptions)); } private final Map, FragmentOptions> fragmentOptions = new HashMap<>(); private final Map starlarkOptions = new HashMap<>(); private Builder() {} } /** Returns the difference between two BuildOptions in a new {@link BuildOptions.OptionsDiff}. */ public static OptionsDiff diff(BuildOptions first, BuildOptions second) { return diff(new OptionsDiff(), first, second); } /** * Returns the difference between two BuildOptions in a pre-existing {@link * BuildOptions.OptionsDiff}. * *

In a single pass through this method, the method can only compare a single ""first"" {@link * BuildOptions} and single ""second"" BuildOptions; but an OptionsDiff instance can store the diff * between a single ""first"" BuildOptions and multiple ""second"" BuildOptions. Being able to * maintain a single OptionsDiff over multiple calls to diff is useful for, for example, * aggregating the difference between a single BuildOptions and the results of applying a {@link * com.google.devtools.build.lib.analysis.config.transitions.SplitTransition}) to it. */ @SuppressWarnings(""ReferenceEquality"") // See comment above == comparison. public static OptionsDiff diff(OptionsDiff diff, BuildOptions first, BuildOptions second) { checkArgument( !diff.hasStarlarkOptions, ""OptionsDiff cannot handle multiple 'second' BuildOptions with Starlark options and is"" + "" trying to diff against %s"", diff); checkNotNull(first); checkNotNull(second); if (first.equals(second)) { return diff; } // Check and report if either class has been trimmed of an options class that exists in the // other. ImmutableSet> firstOptionClasses = first.getNativeOptions().stream() .map(FragmentOptions::getClass) .collect(ImmutableSet.toImmutableSet()); ImmutableSet> secondOptionClasses = second.getNativeOptions().stream() .map(FragmentOptions::getClass) .collect(ImmutableSet.toImmutableSet()); Sets.difference(firstOptionClasses, secondOptionClasses).forEach(diff::addExtraFirstFragment); Sets.difference(secondOptionClasses, firstOptionClasses).stream() .map(second::get) .forEach(diff::addExtraSecondFragment); // For fragments in common, report differences. for (Class clazz : Sets.intersection(firstOptionClasses, secondOptionClasses)) { FragmentOptions firstOptions = first.get(clazz); FragmentOptions secondOptions = second.get(clazz); // We avoid calling #equals because we are going to do a field-by-field comparison anyway. if (firstOptions == secondOptions) { continue; } for (OptionDefinition definition : OptionsParser.getOptionDefinitions(clazz)) { Object firstValue = firstOptions.getValueFromDefinition(definition); Object secondValue = secondOptions.getValueFromDefinition(definition); if (!Objects.equals(firstValue, secondValue)) { diff.addDiff(clazz, definition, firstValue, secondValue); } } } // Compare Starlark options for the two classes. Map starlarkFirst = first.starlarkOptionsMap; Map starlarkSecond = second.starlarkOptionsMap; for (Label buildSetting : Sets.union(starlarkFirst.keySet(), starlarkSecond.keySet())) { if (starlarkFirst.get(buildSetting) == null) { diff.addExtraSecondStarlarkOption(buildSetting, starlarkSecond.get(buildSetting)); } else if (starlarkSecond.get(buildSetting) == null) { diff.addExtraFirstStarlarkOption(buildSetting); } else if (!starlarkFirst.get(buildSetting).equals(starlarkSecond.get(buildSetting))) { diff.putStarlarkDiff( buildSetting, starlarkFirst.get(buildSetting), starlarkSecond.get(buildSetting)); } } return diff; } /** * A diff class for BuildOptions. Fields are meant to be populated and returned by {@link * BuildOptions#diff}. */ public static final class OptionsDiff { private final Multimap, OptionDefinition> differingOptions = ArrayListMultimap.create(); // The keyset for the {@link first} and {@link second} maps are identical and indicate which // specific options differ between the first and second built options. private final Map first = new LinkedHashMap<>(); // Since this class can be used to track the result of transitions, {@link second} is a multimap // to be able to handle {@link SplitTransition}s. private final Multimap second = OrderedSetMultimap.create(); // List of ""extra"" fragments for each BuildOption aka fragments that were trimmed off one // BuildOption but not the other. private final Set> extraFirstFragments = new HashSet<>(); private final Set extraSecondFragments = new HashSet<>(); private final Map starlarkFirst = new LinkedHashMap<>(); // TODO(b/112041323): This should also be multimap but we don't diff multiple times with // Starlark options anywhere yet so add that feature when necessary. private final Map starlarkSecond = new LinkedHashMap<>(); private final List

Called each time an {@link BuildOptions} instance is serialized. */ void prime(BuildOptions options); } /** * Simple {@link OptionsChecksumCache} backed by a {@link ConcurrentMap}. * *

Checksum mappings are retained indefinitely. */ public static final class MapBackedChecksumCache implements OptionsChecksumCache { private final ConcurrentMap map = new ConcurrentHashMap<>(); @Override public BuildOptions getOptions(String checksum) { return map.get(checksum); } @Override public void prime(BuildOptions options) { map.putIfAbsent(options.checksum(), options); } } } ","fragmentOptionClass " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.beans; import java.lang.reflect.Method; import org.apache.harmony.beans.BeansUtils; public class IndexedPropertyDescriptor extends PropertyDescriptor { private Class indexedPropertyType; private Method indexedGetter; private Method indexedSetter; /** * Constructs a new instance of IndexedPropertyDescriptor. * * @param propertyName * the specified indexed property's name. * @param beanClass * the bean class * @param getterName * the name of the array getter * @param setterName * the name of the array setter * @param indexedGetterName * the name of the indexed getter. * @param indexedSetterName * the name of the indexed setter. * @throws IntrospectionException */ public IndexedPropertyDescriptor(String propertyName, Class beanClass, String getterName, String setterName, String indexedGetterName, String indexedSetterName) throws IntrospectionException { super(propertyName, beanClass, getterName, setterName); setIndexedByName(beanClass, indexedGetterName, indexedSetterName); } private void setIndexedByName(Class beanClass, String indexedGetterName, String indexedSetterName) throws IntrospectionException { String theIndexedGetterName = indexedGetterName; if (theIndexedGetterName == null) { if (indexedSetterName != null) { setIndexedWriteMethod(beanClass, indexedSetterName); } } else { if (theIndexedGetterName.length() == 0) { theIndexedGetterName = ""get"" + name; } setIndexedReadMethod(beanClass, theIndexedGetterName); if (indexedSetterName != null) { setIndexedWriteMethod(beanClass, indexedSetterName, indexedPropertyType); } } if (!isCompatible()) { throw new IntrospectionException( ""Property type is incompatible with the indexed property type""); } } private boolean isCompatible() { Class propertyType = getPropertyType(); if (propertyType == null) { return true; } Class componentTypeOfProperty = propertyType.getComponentType(); if (componentTypeOfProperty == null) { return false; } if (indexedPropertyType == null) { return false; } return componentTypeOfProperty.getName().equals( indexedPropertyType.getName()); } /** * Constructs a new instance of IndexedPropertyDescriptor. * * @param propertyName * the specified indexed property's name. * @param getter * the array getter * @param setter * the array setter * @param indexedGetter * the indexed getter * @param indexedSetter * the indexed setter * @throws IntrospectionException */ public IndexedPropertyDescriptor(String propertyName, Method getter, Method setter, Method indexedGetter, Method indexedSetter) throws IntrospectionException { super(propertyName, getter, setter); if (indexedGetter != null) { internalSetIndexedReadMethod(indexedGetter); internalSetIndexedWriteMethod(indexedSetter, true); } else { internalSetIndexedWriteMethod(indexedSetter, true); internalSetIndexedReadMethod(indexedGetter); } if (!isCompatible()) { throw new IntrospectionException( ""Property type is incompatible with the indexed property type""); } } /** * Constructs a new instance of IndexedPropertyDescriptor. * * @param propertyName * the specified indexed property's name. * @param beanClass * the bean class. * @throws IntrospectionException */ public IndexedPropertyDescriptor(String propertyName, Class beanClass) throws IntrospectionException { super(propertyName, beanClass); setIndexedByName(beanClass, ""get"" //$NON-NLS-1$ .concat(initialUpperCase(propertyName)), ""set"" //$NON-NLS-1$ .concat(initialUpperCase(propertyName))); } /** * Sets the indexed getter as the specified method. * * @param indexedGetter * the specified indexed getter. * @throws IntrospectionException */ public void setIndexedReadMethod(Method indexedGetter) throws IntrospectionException { this.internalSetIndexedReadMethod(indexedGetter); } /** * Sets the indexed setter as the specified method. * * @param indexedSetter * the specified indexed setter. * @throws IntrospectionException */ public void setIndexedWriteMethod(Method indexedSetter) throws IntrospectionException { this.internalSetIndexedWriteMethod(indexedSetter, false); } /** * Obtains the indexed setter. * * @return the indexed setter. */ public Method getIndexedWriteMethod() { return indexedSetter; } /** * Obtains the indexed getter. * * @return the indexed getter. */ public Method getIndexedReadMethod() { return indexedGetter; } /** * Determines if this IndexedPropertyDescriptor is equal to * the specified object. Two IndexedPropertyDescriptor s are * equal if the reader, indexed reader, writer, indexed writer, property * types, indexed property type, property editor and flags are equal. * * @param obj * @return true if this indexed property descriptor is equal to the * specified object. */ @Override public boolean equals(Object obj) { if (!(obj instanceof IndexedPropertyDescriptor)) { return false; } IndexedPropertyDescriptor other = (IndexedPropertyDescriptor) obj; return (super.equals(other) && (indexedPropertyType == null ? other.indexedPropertyType == null : indexedPropertyType.equals(other.indexedPropertyType)) && (indexedGetter == null ? other.indexedGetter == null : indexedGetter.equals(other.indexedGetter)) && (indexedSetter == null ? other.indexedSetter == null : indexedSetter.equals(other.indexedSetter))); } /** * HashCode of the IndexedPropertyDescriptor */ @Override public int hashCode() { return super.hashCode() + BeansUtils.getHashCode(indexedPropertyType) + BeansUtils.getHashCode(indexedGetter) + BeansUtils.getHashCode(indexedSetter); } /** * Obtains the Class object of the indexed property type. * * @return the Class object of the indexed property type. */ public Class getIndexedPropertyType() { return indexedPropertyType; } private void setIndexedReadMethod(Class beanClass, String indexedGetterName) throws IntrospectionException { Method getter; try { getter = beanClass.getMethod(indexedGetterName, new Class[] { Integer.TYPE }); } catch (NoSuchMethodException exception) { throw new IntrospectionException(""No such indexed read method""); } catch (SecurityException exception) { throw new IntrospectionException(""Security violation accessing indexed read method""); } internalSetIndexedReadMethod(getter); } private void internalSetIndexedReadMethod(Method indexGetter) throws IntrospectionException { // Clearing the indexed read method. if (indexGetter == null) { if (indexedSetter == null) { if (getPropertyType() != null) { throw new IntrospectionException( ""Indexed read method is not compatible with indexed write method""); } indexedPropertyType = null; } this.indexedGetter = null; return; } // Validate the indexed getter. if ((indexGetter.getParameterTypes().length != 1) || (indexGetter.getParameterTypes()[0] != Integer.TYPE)) { throw new IntrospectionException(""Indexed read method must take a single int argument""); } Class indexedReadType = indexGetter.getReturnType(); if (indexedReadType == Void.TYPE) { throw new IntrospectionException(""Indexed read method must take a single int argument""); } else if (indexedSetter != null && indexGetter.getReturnType() != indexedSetter .getParameterTypes()[1]) { throw new IntrospectionException( ""Indexed read method is not compatible with indexed write method""); } // Set the indexed property type if not already set, confirm validity if // it is. if (this.indexedGetter == null) { indexedPropertyType = indexedReadType; } else { if (indexedPropertyType != indexedReadType) { throw new IntrospectionException( ""Indexed read method is not compatible with indexed write method""); } } // Set the indexed getter this.indexedGetter = indexGetter; } private void setIndexedWriteMethod(Class beanClass, String indexedSetterName) throws IntrospectionException { Method setter = null; try { setter = beanClass.getMethod(indexedSetterName, new Class[] { Integer.TYPE, getPropertyType().getComponentType() }); } catch (SecurityException e) { throw new IntrospectionException(""Security violation accessing indexed write method""); } catch (NoSuchMethodException e) { throw new IntrospectionException(""No such indexed write method""); } internalSetIndexedWriteMethod(setter, true); } private void setIndexedWriteMethod(Class beanClass, String indexedSetterName, Class [MASK] ) throws IntrospectionException { try { Method setter = beanClass.getMethod(indexedSetterName, new Class[] { Integer.TYPE, [MASK] }); internalSetIndexedWriteMethod(setter, true); } catch (NoSuchMethodException exception) { throw new IntrospectionException(""No such indexed write method""); } catch (SecurityException exception) { throw new IntrospectionException(""Security violation accessing indexed write method""); } } private void internalSetIndexedWriteMethod(Method indexSetter, boolean initialize) throws IntrospectionException { // Clearing the indexed write method. if (indexSetter == null) { if (indexedGetter == null) { if (getPropertyType() != null) { throw new IntrospectionException( ""Indexed method is not compatible with non indexed method""); } indexedPropertyType = null; } this.indexedSetter = null; return; } // Validate the indexed write method. Class[] indexedSetterArgs = indexSetter.getParameterTypes(); if (indexedSetterArgs.length != 2) { throw new IntrospectionException(""Indexed write method must take a two arguments""); } if (indexedSetterArgs[0] != Integer.TYPE) { throw new IntrospectionException( ""Indexed write method must take an int as its first argument""); } // Set the indexed property type if not already set, confirm validity if // it is. Class indexedWriteType = indexedSetterArgs[1]; if (initialize && indexedGetter == null) { indexedPropertyType = indexedWriteType; } else { if (indexedPropertyType != indexedWriteType) { throw new IntrospectionException( ""Indexed write method is not compatible with indexed read method""); } } // Set the indexed write method. this.indexedSetter = indexSetter; } private static String initialUpperCase(String string) { if (Character.isUpperCase(string.charAt(0))) { return string; } String initial = string.substring(0, 1).toUpperCase(); return initial.concat(string.substring(1)); } } ","argType " "/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.compression; import com.jcraft.jzlib.Deflater; import com.jcraft.jzlib.JZlib; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.PromiseNotifier; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.ObjectUtil; import java.util.concurrent.TimeUnit; /** * Compresses a {@link ByteBuf} using the deflate algorithm. */ public class JZlibEncoder extends ZlibEncoder { private final int wrapperOverhead; private final Deflater z = new Deflater(); private volatile boolean finished; private volatile ChannelHandlerContext ctx; private static final int THREAD_POOL_DELAY_SECONDS = 10; /** * Creates a new zlib encoder with the default compression level ({@code 6}), * default window bits ({@code 15}), default memory level ({@code 8}), * and the default wrapper ({@link ZlibWrapper#ZLIB}). * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder() { this(6); } /** * Creates a new zlib encoder with the specified {@code compressionLevel}, * default window bits ({@code 15}), default memory level ({@code 8}), * and the default wrapper ({@link ZlibWrapper#ZLIB}). * * @param compressionLevel * {@code 1} yields the fastest compression and {@code 9} yields the * best compression. {@code 0} means no compression. The default * compression level is {@code 6}. * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder(int compressionLevel) { this(ZlibWrapper.ZLIB, compressionLevel); } /** * Creates a new zlib encoder with the default compression level ({@code 6}), * default window bits ({@code 15}), default memory level ({@code 8}), * and the specified wrapper. * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder(ZlibWrapper wrapper) { this(wrapper, 6); } /** * Creates a new zlib encoder with the specified {@code compressionLevel}, * default window bits ({@code 15}), default memory level ({@code 8}), * and the specified wrapper. * * @param compressionLevel * {@code 1} yields the fastest compression and {@code 9} yields the * best compression. {@code 0} means no compression. The default * compression level is {@code 6}. * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder(ZlibWrapper wrapper, int compressionLevel) { this(wrapper, compressionLevel, 15, 8); } /** * Creates a new zlib encoder with the specified {@code compressionLevel}, * the specified {@code windowBits}, the specified {@code memLevel}, and * the specified wrapper. * * @param compressionLevel * {@code 1} yields the fastest compression and {@code 9} yields the * best compression. {@code 0} means no compression. The default * compression level is {@code 6}. * @param windowBits * The base two logarithm of the size of the history buffer. The * value should be in the range {@code 9} to {@code 15} inclusive. * Larger values result in better compression at the expense of * memory usage. The default value is {@code 15}. * @param memLevel * How much memory should be allocated for the internal compression * state. {@code 1} uses minimum memory and {@code 9} uses maximum * memory. Larger values result in better and faster compression * at the expense of memory usage. The default value is {@code 8} * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder(ZlibWrapper wrapper, int compressionLevel, int windowBits, int memLevel) { ObjectUtil.checkInRange(compressionLevel, 0, 9, ""compressionLevel""); ObjectUtil.checkInRange(windowBits, 9, 15, ""windowBits""); ObjectUtil.checkInRange(memLevel, 1, 9, ""memLevel""); ObjectUtil.checkNotNull(wrapper, ""wrapper""); if (wrapper == ZlibWrapper.ZLIB_OR_NONE) { throw new IllegalArgumentException( ""wrapper '"" + ZlibWrapper.ZLIB_OR_NONE + ""' is not "" + ""allowed for compression.""); } int resultCode = z.init( compressionLevel, windowBits, memLevel, ZlibUtil.convertWrapperType(wrapper)); if (resultCode != JZlib.Z_OK) { ZlibUtil.fail(z, ""initialization failure"", resultCode); } wrapperOverhead = ZlibUtil.wrapperOverhead(wrapper); } /** * Creates a new zlib encoder with the default compression level ({@code 6}), * default window bits ({@code 15}), default memory level ({@code 8}), * and the specified preset dictionary. The wrapper is always * {@link ZlibWrapper#ZLIB} because it is the only format that supports * the preset dictionary. * * @param dictionary the preset dictionary * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder(byte[] dictionary) { this(6, dictionary); } /** * Creates a new zlib encoder with the specified {@code compressionLevel}, * default window bits ({@code 15}), default memory level ({@code 8}), * and the specified preset dictionary. The wrapper is always * {@link ZlibWrapper#ZLIB} because it is the only format that supports * the preset dictionary. * * @param compressionLevel * {@code 1} yields the fastest compression and {@code 9} yields the * best compression. {@code 0} means no compression. The default * compression level is {@code 6}. * @param dictionary the preset dictionary * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder(int compressionLevel, byte[] dictionary) { this(compressionLevel, 15, 8, dictionary); } /** * Creates a new zlib encoder with the specified {@code compressionLevel}, * the specified {@code windowBits}, the specified {@code memLevel}, * and the specified preset dictionary. The wrapper is always * {@link ZlibWrapper#ZLIB} because it is the only format that supports * the preset dictionary. * * @param compressionLevel * {@code 1} yields the fastest compression and {@code 9} yields the * best compression. {@code 0} means no compression. The default * compression level is {@code 6}. * @param windowBits * The base two logarithm of the size of the history buffer. The * value should be in the range {@code 9} to {@code 15} inclusive. * Larger values result in better compression at the expense of * memory usage. The default value is {@code 15}. * @param memLevel * How much memory should be allocated for the internal compression * state. {@code 1} uses minimum memory and {@code 9} uses maximum * memory. Larger values result in better and faster compression * at the expense of memory usage. The default value is {@code 8} * @param dictionary the preset dictionary * * @throws CompressionException if failed to initialize zlib */ public JZlibEncoder(int compressionLevel, int windowBits, int memLevel, byte[] dictionary) { ObjectUtil.checkInRange(compressionLevel, 0, 9, ""compressionLevel""); ObjectUtil.checkInRange(windowBits, 9, 15, ""windowBits""); ObjectUtil.checkInRange(memLevel, 1, 9, ""memLevel""); ObjectUtil.checkNotNull(dictionary, ""dictionary""); int resultCode; resultCode = z.deflateInit( compressionLevel, windowBits, memLevel, JZlib.W_ZLIB); // Default: ZLIB format if (resultCode != JZlib.Z_OK) { ZlibUtil.fail(z, ""initialization failure"", resultCode); } else { resultCode = z.deflateSetDictionary(dictionary, dictionary.length); if (resultCode != JZlib.Z_OK) { ZlibUtil.fail(z, ""failed to set the dictionary"", resultCode); } } wrapperOverhead = ZlibUtil.wrapperOverhead(ZlibWrapper.ZLIB); } @Override public ChannelFuture close() { return close(ctx().channel().newPromise()); } @Override public ChannelFuture close(final ChannelPromise promise) { ChannelHandlerContext ctx = ctx(); EventExecutor [MASK] = ctx. [MASK] (); if ( [MASK] .inEventLoop()) { return finishEncode(ctx, promise); } else { final ChannelPromise p = ctx.newPromise(); [MASK] .execute(new Runnable() { @Override public void run() { ChannelFuture f = finishEncode(ctx(), p); PromiseNotifier.cascade(f, promise); } }); return p; } } private ChannelHandlerContext ctx() { ChannelHandlerContext ctx = this.ctx; if (ctx == null) { throw new IllegalStateException(""not added to a pipeline""); } return ctx; } @Override public boolean isClosed() { return finished; } @Override protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception { if (finished) { out.writeBytes(in); return; } int inputLength = in.readableBytes(); if (inputLength == 0) { return; } try { // Configure input. boolean inHasArray = in.hasArray(); z.avail_in = inputLength; if (inHasArray) { z.next_in = in.array(); z.next_in_index = in.arrayOffset() + in.readerIndex(); } else { byte[] array = new byte[inputLength]; in.getBytes(in.readerIndex(), array); z.next_in = array; z.next_in_index = 0; } int oldNextInIndex = z.next_in_index; // Configure output. int maxOutputLength = (int) Math.ceil(inputLength * 1.001) + 12 + wrapperOverhead; out.ensureWritable(maxOutputLength); z.avail_out = maxOutputLength; z.next_out = out.array(); z.next_out_index = out.arrayOffset() + out.writerIndex(); int oldNextOutIndex = z.next_out_index; // Note that Z_PARTIAL_FLUSH has been deprecated. int resultCode; try { resultCode = z.deflate(JZlib.Z_SYNC_FLUSH); } finally { in.skipBytes(z.next_in_index - oldNextInIndex); } if (resultCode != JZlib.Z_OK) { ZlibUtil.fail(z, ""compression failure"", resultCode); } int outputLength = z.next_out_index - oldNextOutIndex; if (outputLength > 0) { out.writerIndex(out.writerIndex() + outputLength); } } finally { // Deference the external references explicitly to tell the VM that // the allocated byte arrays are temporary so that the call stack // can be utilized. // I'm not sure if the modern VMs do this optimization though. z.next_in = null; z.next_out = null; } } @Override public void close( final ChannelHandlerContext ctx, final ChannelPromise promise) { ChannelFuture f = finishEncode(ctx, ctx.newPromise()); if (!f.isDone()) { // Ensure the channel is closed even if the write operation completes in time. final Future future = ctx. [MASK] ().schedule(new Runnable() { @Override public void run() { if (!promise.isDone()) { ctx.close(promise); } } }, THREAD_POOL_DELAY_SECONDS, TimeUnit.SECONDS); f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) { // Cancel the scheduled timeout. future.cancel(true); if (!promise.isDone()) { ctx.close(promise); } } }); } else { ctx.close(promise); } } private ChannelFuture finishEncode(ChannelHandlerContext ctx, ChannelPromise promise) { if (finished) { promise.setSuccess(); return promise; } finished = true; ByteBuf footer; try { // Configure input. z.next_in = EmptyArrays.EMPTY_BYTES; z.next_in_index = 0; z.avail_in = 0; // Configure output. byte[] out = new byte[32]; // room for ADLER32 + ZLIB / CRC32 + GZIP header z.next_out = out; z.next_out_index = 0; z.avail_out = out.length; // Write the ADLER32 checksum (stream footer). int resultCode = z.deflate(JZlib.Z_FINISH); if (resultCode != JZlib.Z_OK && resultCode != JZlib.Z_STREAM_END) { promise.setFailure(ZlibUtil.deflaterException(z, ""compression failure"", resultCode)); return promise; } else if (z.next_out_index != 0) { // Suppressed a warning above to be on the safe side // even if z.next_out_index seems to be always 0 here footer = Unpooled.wrappedBuffer(out, 0, z.next_out_index); } else { footer = Unpooled.EMPTY_BUFFER; } } finally { z.deflateEnd(); // Deference the external references explicitly to tell the VM that // the allocated byte arrays are temporary so that the call stack // can be utilized. // I'm not sure if the modern VMs do this optimization though. z.next_in = null; z.next_out = null; } return ctx.writeAndFlush(footer, promise); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; } } ","executor " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.utils; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.CharBuffer; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import com.badlogic.gdx.math.Matrix3; import com.badlogic.gdx.math.Matrix4; /** Class with static helper methods to increase the speed of array/direct buffer and direct buffer/direct buffer transfers * * @author mzechner, xoppa */ public final class BufferUtils { private BufferUtils () { } static Array unsafeBuffers = new Array(); static int allocatedUnsafe = 0; /** Copies numFloats floats from src starting at offset to dst. Dst is assumed to be a direct {@link Buffer}. The method will * crash if that is not the case. The position and limit of the buffer are ignored, the copy is placed at position 0 in the * buffer. After the copying process the position of the buffer is set to 0 and its limit is set to numFloats * 4 if it is a * ByteBuffer and numFloats if it is a FloatBuffer. In case the Buffer is neither a ByteBuffer nor a FloatBuffer the limit is * not set. This is an expert method, use at your own risk. * * @param src the source array * @param dst the destination buffer, has to be a direct Buffer * @param numFloats the number of floats to copy * @param offset the offset in src to start copying from */ public static void copy (float[] src, Buffer dst, int numFloats, int offset) { if (dst instanceof ByteBuffer) dst.limit(numFloats << 2); else if (dst instanceof FloatBuffer) dst.limit(numFloats); copyJni(src, dst, numFloats, offset); dst.position(0); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position will stay the same, * the limit will be set to position + numElements. The Buffer must be a direct Buffer with native byte order. No error * checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param dst the destination Buffer, its position is used as an offset. * @param numElements the number of elements to copy. */ public static void copy (byte[] src, int srcOffset, Buffer dst, int numElements) { dst.limit(dst.position() + bytesToElements(dst, numElements)); copyJni(src, srcOffset, dst, positionInBytes(dst), numElements); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position will stay the same, * the limit will be set to position + numElements. The Buffer must be a direct Buffer with native byte order. No error * checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param dst the destination Buffer, its position is used as an offset. * @param numElements the number of elements to copy. */ public static void copy (short[] src, int srcOffset, Buffer dst, int numElements) { dst.limit(dst.position() + bytesToElements(dst, numElements << 1)); copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 1); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position and limit will stay * the same. The Buffer must be a direct Buffer with native byte order. No error checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param numElements the number of elements to copy. * @param dst the destination Buffer, its position is used as an offset. */ public static void copy (char[] src, int srcOffset, int numElements, Buffer dst) { copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 1); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position and limit will stay * the same. The Buffer must be a direct Buffer with native byte order. No error checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param numElements the number of elements to copy. * @param dst the destination Buffer, its position is used as an offset. */ public static void copy (int[] src, int srcOffset, int numElements, Buffer dst) { copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 2); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position and limit will stay * the same. The Buffer must be a direct Buffer with native byte order. No error checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param numElements the number of elements to copy. * @param dst the destination Buffer, its position is used as an offset. */ public static void copy (long[] src, int srcOffset, int numElements, Buffer dst) { copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 3); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position and limit will stay * the same. The Buffer must be a direct Buffer with native byte order. No error checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param numElements the number of elements to copy. * @param dst the destination Buffer, its position is used as an offset. */ public static void copy (float[] src, int srcOffset, int numElements, Buffer dst) { copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 2); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position and limit will stay * the same. The Buffer must be a direct Buffer with native byte order. No error checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param numElements the number of elements to copy. * @param dst the destination Buffer, its position is used as an offset. */ public static void copy (double[] src, int srcOffset, int numElements, Buffer dst) { copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 3); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position will stay the same, * the limit will be set to position + numElements. The Buffer must be a direct Buffer with native byte order. No error * checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param dst the destination Buffer, its position is used as an offset. * @param numElements the number of elements to copy. */ public static void copy (char[] src, int srcOffset, Buffer dst, int numElements) { dst.limit(dst.position() + bytesToElements(dst, numElements << 1)); copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 1); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position will stay the same, * the limit will be set to position + numElements. The Buffer must be a direct Buffer with native byte order. No error * checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param dst the destination Buffer, its position is used as an offset. * @param numElements the number of elements to copy. */ public static void copy (int[] src, int srcOffset, Buffer dst, int numElements) { dst.limit(dst.position() + bytesToElements(dst, numElements << 2)); copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 2); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position will stay the same, * the limit will be set to position + numElements. The Buffer must be a direct Buffer with native byte order. No error * checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param dst the destination Buffer, its position is used as an offset. * @param numElements the number of elements to copy. */ public static void copy (long[] src, int srcOffset, Buffer dst, int numElements) { dst.limit(dst.position() + bytesToElements(dst, numElements << 3)); copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 3); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position will stay the same, * the limit will be set to position + numElements. The Buffer must be a direct Buffer with native byte order. No error * checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param dst the destination Buffer, its position is used as an offset. * @param numElements the number of elements to copy. */ public static void copy (float[] src, int srcOffset, Buffer dst, int numElements) { dst.limit(dst.position() + bytesToElements(dst, numElements << 2)); copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 2); } /** Copies the contents of src to dst, starting from src[srcOffset], copying numElements elements. The {@link Buffer} * instance's {@link Buffer#position()} is used to define the offset into the Buffer itself. The position will stay the same, * the limit will be set to position + numElements. The Buffer must be a direct Buffer with native byte order. No error * checking is performed. * * @param src the source array. * @param srcOffset the offset into the source array. * @param dst the destination Buffer, its position is used as an offset. * @param numElements the number of elements to copy. */ public static void copy (double[] src, int srcOffset, Buffer dst, int numElements) { dst.limit(dst.position() + bytesToElements(dst, numElements << 3)); copyJni(src, srcOffset, dst, positionInBytes(dst), numElements << 3); } /** Copies the contents of src to dst, starting from the current position of src, copying numElements elements (using the data * type of src, no matter the datatype of dst). The dst {@link Buffer#position()} is used as the writing offset. The position * of both Buffers will stay the same. The limit of the src Buffer will stay the same. The limit of the dst Buffer will be set * to dst.position() + numElements, where numElements are translated to the number of elements appropriate for the dst Buffer * data type. The Buffers must be direct Buffers with native byte order. No error checking is performed. * * @param src the source Buffer. * @param dst the destination Buffer. * @param numElements the number of elements to copy. */ public static void copy (Buffer src, Buffer dst, int numElements) { int numBytes = elementsToBytes(src, numElements); dst.limit(dst.position() + bytesToElements(dst, numBytes)); copyJni(src, positionInBytes(src), dst, positionInBytes(dst), numBytes); } /** Multiply float vector components within the buffer with the specified matrix. The {@link Buffer#position()} is used as the * offset. * @param data The buffer to transform. * @param dimensions The number of components of the vector (2 for xy, 3 for xyz or 4 for xyzw) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with */ public static void transform (Buffer data, int dimensions, int [MASK] , int count, Matrix4 matrix) { transform(data, dimensions, [MASK] , count, matrix, 0); } /** Multiply float vector components within the buffer with the specified matrix. The {@link Buffer#position()} is used as the * offset. * @param data The buffer to transform. * @param dimensions The number of components of the vector (2 for xy, 3 for xyz or 4 for xyzw) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with */ public static void transform (float[] data, int dimensions, int [MASK] , int count, Matrix4 matrix) { transform(data, dimensions, [MASK] , count, matrix, 0); } /** Multiply float vector components within the buffer with the specified matrix. The specified offset value is added to the * {@link Buffer#position()} and used as the offset. * @param data The buffer to transform. * @param dimensions The number of components of the vector (2 for xy, 3 for xyz or 4 for xyzw) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with * @param offset The offset within the buffer (in bytes relative to the current position) to the vector */ public static void transform (Buffer data, int dimensions, int [MASK] , int count, Matrix4 matrix, int offset) { switch (dimensions) { case 4: transformV4M4Jni(data, [MASK] , count, matrix.val, positionInBytes(data) + offset); break; case 3: transformV3M4Jni(data, [MASK] , count, matrix.val, positionInBytes(data) + offset); break; case 2: transformV2M4Jni(data, [MASK] , count, matrix.val, positionInBytes(data) + offset); break; default: throw new IllegalArgumentException(); } } /** Multiply float vector components within the buffer with the specified matrix. The specified offset value is added to the * {@link Buffer#position()} and used as the offset. * @param data The buffer to transform. * @param dimensions The number of components of the vector (2 for xy, 3 for xyz or 4 for xyzw) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with * @param offset The offset within the buffer (in bytes relative to the current position) to the vector */ public static void transform (float[] data, int dimensions, int [MASK] , int count, Matrix4 matrix, int offset) { switch (dimensions) { case 4: transformV4M4Jni(data, [MASK] , count, matrix.val, offset); break; case 3: transformV3M4Jni(data, [MASK] , count, matrix.val, offset); break; case 2: transformV2M4Jni(data, [MASK] , count, matrix.val, offset); break; default: throw new IllegalArgumentException(); } } /** Multiply float vector components within the buffer with the specified matrix. The {@link Buffer#position()} is used as the * offset. * @param data The buffer to transform. * @param dimensions The number of components (x, y, z) of the vector (2 for xy or 3 for xyz) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with */ public static void transform (Buffer data, int dimensions, int [MASK] , int count, Matrix3 matrix) { transform(data, dimensions, [MASK] , count, matrix, 0); } /** Multiply float vector components within the buffer with the specified matrix. The {@link Buffer#position()} is used as the * offset. * @param data The buffer to transform. * @param dimensions The number of components (x, y, z) of the vector (2 for xy or 3 for xyz) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with */ public static void transform (float[] data, int dimensions, int [MASK] , int count, Matrix3 matrix) { transform(data, dimensions, [MASK] , count, matrix, 0); } /** Multiply float vector components within the buffer with the specified matrix. The specified offset value is added to the * {@link Buffer#position()} and used as the offset. * @param data The buffer to transform. * @param dimensions The number of components (x, y, z) of the vector (2 for xy or 3 for xyz) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with, * @param offset The offset within the buffer (in bytes relative to the current position) to the vector */ public static void transform (Buffer data, int dimensions, int [MASK] , int count, Matrix3 matrix, int offset) { switch (dimensions) { case 3: transformV3M3Jni(data, [MASK] , count, matrix.val, positionInBytes(data) + offset); break; case 2: transformV2M3Jni(data, [MASK] , count, matrix.val, positionInBytes(data) + offset); break; default: throw new IllegalArgumentException(); } } /** Multiply float vector components within the buffer with the specified matrix. The specified offset value is added to the * {@link Buffer#position()} and used as the offset. * @param data The buffer to transform. * @param dimensions The number of components (x, y, z) of the vector (2 for xy or 3 for xyz) * @param [MASK] The offset between the first and the second vector to transform * @param count The number of vectors to transform * @param matrix The matrix to multiply the vector with, * @param offset The offset within the buffer (in bytes relative to the current position) to the vector */ public static void transform (float[] data, int dimensions, int [MASK] , int count, Matrix3 matrix, int offset) { switch (dimensions) { case 3: transformV3M3Jni(data, [MASK] , count, matrix.val, offset); break; case 2: transformV2M3Jni(data, [MASK] , count, matrix.val, offset); break; default: throw new IllegalArgumentException(); } } public static long findFloats (Buffer vertex, int [MASK] , Buffer vertices, int numVertices) { return find(vertex, positionInBytes(vertex), [MASK] , vertices, positionInBytes(vertices), numVertices); } public static long findFloats (float[] vertex, int [MASK] , Buffer vertices, int numVertices) { return find(vertex, 0, [MASK] , vertices, positionInBytes(vertices), numVertices); } public static long findFloats (Buffer vertex, int [MASK] , float[] vertices, int numVertices) { return find(vertex, positionInBytes(vertex), [MASK] , vertices, 0, numVertices); } public static long findFloats (float[] vertex, int [MASK] , float[] vertices, int numVertices) { return find(vertex, 0, [MASK] , vertices, 0, numVertices); } public static long findFloats (Buffer vertex, int [MASK] , Buffer vertices, int numVertices, float epsilon) { return find(vertex, positionInBytes(vertex), [MASK] , vertices, positionInBytes(vertices), numVertices, epsilon); } public static long findFloats (float[] vertex, int [MASK] , Buffer vertices, int numVertices, float epsilon) { return find(vertex, 0, [MASK] , vertices, positionInBytes(vertices), numVertices, epsilon); } public static long findFloats (Buffer vertex, int [MASK] , float[] vertices, int numVertices, float epsilon) { return find(vertex, positionInBytes(vertex), [MASK] , vertices, 0, numVertices, epsilon); } public static long findFloats (float[] vertex, int [MASK] , float[] vertices, int numVertices, float epsilon) { return find(vertex, 0, [MASK] , vertices, 0, numVertices, epsilon); } private static int positionInBytes (Buffer dst) { if (dst instanceof ByteBuffer) return dst.position(); else if (dst instanceof ShortBuffer) return dst.position() << 1; else if (dst instanceof CharBuffer) return dst.position() << 1; else if (dst instanceof IntBuffer) return dst.position() << 2; else if (dst instanceof LongBuffer) return dst.position() << 3; else if (dst instanceof FloatBuffer) return dst.position() << 2; else if (dst instanceof DoubleBuffer) return dst.position() << 3; else throw new GdxRuntimeException(""Can't copy to a "" + dst.getClass().getName() + "" instance""); } private static int bytesToElements (Buffer dst, int bytes) { if (dst instanceof ByteBuffer) return bytes; else if (dst instanceof ShortBuffer) return bytes >>> 1; else if (dst instanceof CharBuffer) return bytes >>> 1; else if (dst instanceof IntBuffer) return bytes >>> 2; else if (dst instanceof LongBuffer) return bytes >>> 3; else if (dst instanceof FloatBuffer) return bytes >>> 2; else if (dst instanceof DoubleBuffer) return bytes >>> 3; else throw new GdxRuntimeException(""Can't copy to a "" + dst.getClass().getName() + "" instance""); } private static int elementsToBytes (Buffer dst, int elements) { if (dst instanceof ByteBuffer) return elements; else if (dst instanceof ShortBuffer) return elements << 1; else if (dst instanceof CharBuffer) return elements << 1; else if (dst instanceof IntBuffer) return elements << 2; else if (dst instanceof LongBuffer) return elements << 3; else if (dst instanceof FloatBuffer) return elements << 2; else if (dst instanceof DoubleBuffer) return elements << 3; else throw new GdxRuntimeException(""Can't copy to a "" + dst.getClass().getName() + "" instance""); } public static FloatBuffer newFloatBuffer (int numFloats) { ByteBuffer buffer = ByteBuffer.allocateDirect(numFloats * 4); buffer.order(ByteOrder.nativeOrder()); return buffer.asFloatBuffer(); } public static DoubleBuffer newDoubleBuffer (int numDoubles) { ByteBuffer buffer = ByteBuffer.allocateDirect(numDoubles * 8); buffer.order(ByteOrder.nativeOrder()); return buffer.asDoubleBuffer(); } public static ByteBuffer newByteBuffer (int numBytes) { ByteBuffer buffer = ByteBuffer.allocateDirect(numBytes); buffer.order(ByteOrder.nativeOrder()); return buffer; } public static ShortBuffer newShortBuffer (int numShorts) { ByteBuffer buffer = ByteBuffer.allocateDirect(numShorts * 2); buffer.order(ByteOrder.nativeOrder()); return buffer.asShortBuffer(); } public static CharBuffer newCharBuffer (int numChars) { ByteBuffer buffer = ByteBuffer.allocateDirect(numChars * 2); buffer.order(ByteOrder.nativeOrder()); return buffer.asCharBuffer(); } public static IntBuffer newIntBuffer (int numInts) { ByteBuffer buffer = ByteBuffer.allocateDirect(numInts * 4); buffer.order(ByteOrder.nativeOrder()); return buffer.asIntBuffer(); } public static LongBuffer newLongBuffer (int numLongs) { ByteBuffer buffer = ByteBuffer.allocateDirect(numLongs * 8); buffer.order(ByteOrder.nativeOrder()); return buffer.asLongBuffer(); } public static void disposeUnsafeByteBuffer (ByteBuffer buffer) { int size = buffer.capacity(); synchronized (unsafeBuffers) { if (!unsafeBuffers.removeValue(buffer, true)) throw new IllegalArgumentException(""buffer not allocated with newUnsafeByteBuffer or already disposed""); } allocatedUnsafe -= size; freeMemory(buffer); } public static boolean isUnsafeByteBuffer (ByteBuffer buffer) { synchronized (unsafeBuffers) { return unsafeBuffers.contains(buffer, true); } } /** Allocates a new direct ByteBuffer from native heap memory using the native byte order. Needs to be disposed with * {@link #disposeUnsafeByteBuffer(ByteBuffer)}. */ public static ByteBuffer newUnsafeByteBuffer (int numBytes) { ByteBuffer buffer = newDisposableByteBuffer(numBytes); buffer.order(ByteOrder.nativeOrder()); allocatedUnsafe += numBytes; synchronized (unsafeBuffers) { unsafeBuffers.add(buffer); } return buffer; } /** Returns the address of the Buffer, it assumes it is an unsafe buffer. * @param buffer The Buffer to ask the address for. * @return the address of the Buffer. */ public static long getUnsafeBufferAddress (Buffer buffer) { return getBufferAddress(buffer) + buffer.position(); } /** Registers the given ByteBuffer as an unsafe ByteBuffer. The ByteBuffer must have been allocated in native code, pointing to * a memory region allocated via malloc. Needs to be disposed with {@link #disposeUnsafeByteBuffer(ByteBuffer)}. * @param buffer the {@link ByteBuffer} to register * @return the ByteBuffer passed to the method */ public static ByteBuffer newUnsafeByteBuffer (ByteBuffer buffer) { allocatedUnsafe += buffer.capacity(); synchronized (unsafeBuffers) { unsafeBuffers.add(buffer); } return buffer; } /** @return the number of bytes allocated with {@link #newUnsafeByteBuffer(int)} */ public static int getAllocatedBytesUnsafe () { return allocatedUnsafe; } // @off /*JNI #include #include #include */ /** Frees the memory allocated for the ByteBuffer, which MUST have been allocated via {@link #newUnsafeByteBuffer(ByteBuffer)} * or in native code. */ private static native void freeMemory (ByteBuffer buffer); /* free(buffer); */ private static native ByteBuffer newDisposableByteBuffer (int numBytes); /* return env->NewDirectByteBuffer((char*)malloc(numBytes), numBytes); */ private static native long getBufferAddress (Buffer buffer); /* return (jlong) buffer; */ /** Writes the specified number of zeros to the buffer. This is generally faster than reallocating a new buffer. */ public static native void clear (ByteBuffer buffer, int numBytes); /* memset(buffer, 0, numBytes); */ private native static void copyJni (float[] src, Buffer dst, int numFloats, int offset); /* memcpy(dst, src + offset, numFloats << 2 ); */ private native static void copyJni (byte[] src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ private native static void copyJni (char[] src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ private native static void copyJni (short[] src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ private native static void copyJni (int[] src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ private native static void copyJni (long[] src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ private native static void copyJni (float[] src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ private native static void copyJni (double[] src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ private native static void copyJni (Buffer src, int srcOffset, Buffer dst, int dstOffset, int numBytes); /* memcpy(dst + dstOffset, src + srcOffset, numBytes); */ /*JNI template void transform(float * const &src, float * const &m, float * const &dst) {} template<> inline void transform<4, 4>(float * const &src, float * const &m, float * const &dst) { const float x = src[0], y = src[1], z = src[2], w = src[3]; dst[0] = x * m[ 0] + y * m[ 4] + z * m[ 8] + w * m[12]; dst[1] = x * m[ 1] + y * m[ 5] + z * m[ 9] + w * m[13]; dst[2] = x * m[ 2] + y * m[ 6] + z * m[10] + w * m[14]; dst[3] = x * m[ 3] + y * m[ 7] + z * m[11] + w * m[15]; } template<> inline void transform<3, 4>(float * const &src, float * const &m, float * const &dst) { const float x = src[0], y = src[1], z = src[2]; dst[0] = x * m[ 0] + y * m[ 4] + z * m[ 8] + m[12]; dst[1] = x * m[ 1] + y * m[ 5] + z * m[ 9] + m[13]; dst[2] = x * m[ 2] + y * m[ 6] + z * m[10] + m[14]; } template<> inline void transform<2, 4>(float * const &src, float * const &m, float * const &dst) { const float x = src[0], y = src[1]; dst[0] = x * m[ 0] + y * m[ 4] + m[12]; dst[1] = x * m[ 1] + y * m[ 5] + m[13]; } template<> inline void transform<3, 3>(float * const &src, float * const &m, float * const &dst) { const float x = src[0], y = src[1], z = src[2]; dst[0] = x * m[0] + y * m[3] + z * m[6]; dst[1] = x * m[1] + y * m[4] + z * m[7]; dst[2] = x * m[2] + y * m[5] + z * m[8]; } template<> inline void transform<2, 3>(float * const &src, float * const &m, float * const &dst) { const float x = src[0], y = src[1]; dst[0] = x * m[0] + y * m[3] + m[6]; dst[1] = x * m[1] + y * m[4] + m[7]; } template void transform(float * const &v, int const &stride, int const &count, float * const &m, int offset) { for (int i = 0; i < count; i++) { transform(&v[offset], m, &v[offset]); offset += stride; } } template void transform(float * const &v, int const &stride, unsigned short * const &indices, int const &count, float * const &m, int offset) { for (int i = 0; i < count; i++) { transform(&v[offset], m, &v[offset]); offset += stride; } } inline bool compare(float * const &lhs, float * const & rhs, const unsigned int &size, const float &epsilon) { for (unsigned int i = 0; i < size; i++) if ((*(unsigned int*)&lhs[i] != *(unsigned int*)&rhs[i]) && ((lhs[i] > rhs[i] ? lhs[i] - rhs[i] : rhs[i] - lhs[i]) > epsilon)) return false; return true; } long find(float * const &vertex, const unsigned int &size, float * const &vertices, const unsigned int &count, const float &epsilon) { for (unsigned int i = 0; i < count; i++) if (compare(&vertices[i*size], vertex, size, epsilon)) return (long)i; return -1; } inline bool compare(float * const &lhs, float * const & rhs, const unsigned int &size) { for (unsigned int i = 0; i < size; i++) if ((*(unsigned int*)&lhs[i] != *(unsigned int*)&rhs[i]) && lhs[i] != rhs[i]) return false; return true; } long find(float * const &vertex, const unsigned int &size, float * const &vertices, const unsigned int &count) { for (unsigned int i = 0; i < count; i++) if (compare(&vertices[i*size], vertex, size)) return (long)i; return -1; } inline unsigned int calcHash(float * const &vertex, const unsigned int &size) { unsigned int result = 0; for (unsigned int i = 0; i < size; ++i) result += ((*((unsigned int *)&vertex[i])) & 0xffffff80) >> (i & 0x7); return result & 0x7fffffff; } long find(float * const &vertex, const unsigned int &size, float * const &vertices, unsigned int * const &hashes, const unsigned int &count) { const unsigned int hash = calcHash(vertex, size); for (unsigned int i = 0; i < count; i++) if (hashes[i] == hash && compare(&vertices[i*size], vertex, size)) return (long)i; return -1; } */ private native static void transformV4M4Jni (Buffer data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<4, 4>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV4M4Jni (float[] data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<4, 4>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV3M4Jni (Buffer data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<3, 4>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV3M4Jni (float[] data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<3, 4>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV2M4Jni (Buffer data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<2, 4>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV2M4Jni (float[] data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<2, 4>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV3M3Jni (Buffer data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<3, 3>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV3M3Jni (float[] data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<3, 3>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV2M3Jni (Buffer data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<2, 3>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static void transformV2M3Jni (float[] data, int [MASK] , int count, float[] matrix, int offsetInBytes); /* transform<2, 3>((float*)data, [MASK] / 4, count, (float*)matrix, offsetInBytes / 4); */ private native static long find(Buffer vertex, int vertexOffsetInBytes, int [MASK] , Buffer vertices, int verticesOffsetInBytes, int numVertices); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices); */ private native static long find(float[] vertex, int vertexOffsetInBytes, int [MASK] , Buffer vertices, int verticesOffsetInBytes, int numVertices); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices); */ private native static long find(Buffer vertex, int vertexOffsetInBytes, int [MASK] , float[] vertices, int verticesOffsetInBytes, int numVertices); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices); */ private native static long find(float[] vertex, int vertexOffsetInBytes, int [MASK] , float[] vertices, int verticesOffsetInBytes, int numVertices); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices); */ private native static long find(Buffer vertex, int vertexOffsetInBytes, int [MASK] , Buffer vertices, int verticesOffsetInBytes, int numVertices, float epsilon); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices, epsilon); */ private native static long find(float[] vertex, int vertexOffsetInBytes, int [MASK] , Buffer vertices, int verticesOffsetInBytes, int numVertices, float epsilon); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices, epsilon); */ private native static long find(Buffer vertex, int vertexOffsetInBytes, int [MASK] , float[] vertices, int verticesOffsetInBytes, int numVertices, float epsilon); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices, epsilon); */ private native static long find(float[] vertex, int vertexOffsetInBytes, int [MASK] , float[] vertices, int verticesOffsetInBytes, int numVertices, float epsilon); /* return find((float *)&vertex[vertexOffsetInBytes / 4], (unsigned int)( [MASK] / 4), (float*)&vertices[verticesOffsetInBytes / 4], (unsigned int)numVertices, epsilon); */ } ","strideInBytes " "// Copyright 2022 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.analysis; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Comparator.comparing; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.devtools.build.lib.actions.ActionExecutionContext; import com.google.devtools.build.lib.actions.ActionKeyContext; import com.google.devtools.build.lib.actions.ActionOwner; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; import com.google.devtools.build.lib.actions.CommandLineExpansionException; import com.google.devtools.build.lib.actions.CommandLineItem.MapFn; import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction; import com.google.devtools.build.lib.analysis.actions.DeterministicWriter; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.RepositoryMapping; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.packages.Package; import com.google.devtools.build.lib.util.Fingerprint; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Map.Entry; import java.util.UUID; import javax.annotation.Nullable; import net.starlark.java.eval.EvalException; /** Creates a manifest file describing the repos and mappings relevant for a runfile tree. */ public final class RepoMappingManifestAction extends AbstractFileWriteAction implements AbstractFileWriteAction.FileContentsProvider { private static final UUID MY_UUID = UUID.fromString(""458e351c-4d30-433d-b927-da6cddd4737f""); // Uses MapFn's args parameter just like Fingerprint#addString to compute a cacheable fingerprint // of just the repo name and mapping of a given Package. private static final MapFn REPO_AND_MAPPING_DIGEST_FN = (pkg, args) -> { args.accept(pkg.getPackageIdentifier().getRepository().getName()); var mapping = pkg.getRepositoryMapping().entries(); args.accept(String.valueOf(mapping.size())); mapping.forEach( (apparentName, canonicalName) -> { args.accept(apparentName); args.accept(canonicalName.getName()); }); }; private static final MapFn OWNER_REPO_FN = (artifact, args) -> { args.accept( artifact.getOwner() != null ? artifact.getOwner().getRepository().getName() : """"); }; private static final MapFn FIRST_SEGMENT_FN = (symlink, args) -> args.accept(symlink.getPath().getSegment(0)); private final NestedSet transitivePackages; private final NestedSet runfilesArtifacts; private final boolean hasRunfilesSymlinks; private final NestedSet runfilesRootSymlinks; private final String workspaceName; public RepoMappingManifestAction( ActionOwner owner, Artifact output, NestedSet transitivePackages, NestedSet runfilesArtifacts, NestedSet runfilesSymlinks, NestedSet runfilesRootSymlinks, String workspaceName) { super( owner, NestedSetBuilder.emptySet(Order.STABLE_ORDER), output, /* makeExecutable= */ false); this.transitivePackages = transitivePackages; this.runfilesArtifacts = runfilesArtifacts; this.hasRunfilesSymlinks = !runfilesSymlinks.isEmpty(); this.runfilesRootSymlinks = runfilesRootSymlinks; this.workspaceName = workspaceName; } @Override public String getMnemonic() { return ""RepoMappingManifest""; } @Override protected String getRawProgressMessage() { return ""Writing repo mapping manifest for "" + getOwner().getLabel(); } @Override protected void computeKey( ActionKeyContext actionKeyContext, @Nullable ArtifactExpander artifactExpander, Fingerprint fp) throws CommandLineExpansionException, EvalException, InterruptedException { fp.addUUID(MY_UUID); actionKeyContext.addNestedSetToFingerprint(REPO_AND_MAPPING_DIGEST_FN, fp, transitivePackages); actionKeyContext.addNestedSetToFingerprint(OWNER_REPO_FN, fp, runfilesArtifacts); fp.addBoolean(hasRunfilesSymlinks); actionKeyContext.addNestedSetToFingerprint(FIRST_SEGMENT_FN, fp, runfilesRootSymlinks); fp.addString(workspaceName); } /** * Get the contents of a file internally using an in memory output stream. * * @return returns the file contents as a string. */ @Override public String getFileContents(@Nullable EventHandler eventHandler) throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); newDeterministicWriter().writeOutputFile(stream); return stream.toString(UTF_8); } @Override public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx) { return newDeterministicWriter(); } public DeterministicWriter newDeterministicWriter() { return out -> { PrintWriter writer = new PrintWriter(out, /* autoFlush= */ false, ISO_8859_1); var reposInRunfilePaths = ImmutableSet.builder(); // The runfiles paths of symlinks are always prefixed with the main workspace name, *not* the // name of the repository adding the symlink. if (hasRunfilesSymlinks) { reposInRunfilePaths.add(RepositoryName.MAIN.getName()); } // Since root symlinks are the only way to stage a runfile at a specific path under the // current repository's runfiles directory, recognize canonical repository names that appear // as the first segment of their runfiles paths. for (SymlinkEntry symlink : runfilesRootSymlinks.toList()) { reposInRunfilePaths.add(symlink.getPath().getSegment(0)); } for (Artifact artifact : runfilesArtifacts.toList()) { Label owner = artifact.getOwner(); if (owner != null) { reposInRunfilePaths.add(owner.getRepository().getName()); } } transitivePackages.toList().stream() .collect( toImmutableSortedMap( comparing(RepositoryName::getName), pkg -> pkg.getPackageIdentifier().getRepository(), Package::getRepositoryMapping, // All packages in a given repository have the same repository mapping, so the // particular way of resolving duplicates does not matter. (first, second) -> first)) .forEach( (repoName, mapping) -> writeRepoMapping(writer, reposInRunfilePaths.build(), repoName, mapping)); writer.flush(); }; } private void writeRepoMapping( PrintWriter writer, ImmutableSet reposInRunfilesPaths, RepositoryName repoName, RepositoryMapping repoMapping) { for (Entry mappingEntry : ImmutableSortedMap.copyOf(repoMapping.entries()).entrySet()) { if (mappingEntry.getKey().isEmpty()) { // The apparent repo name can only be empty for the main repo. We skip this line as // Rlocation paths can't reference an empty apparent name anyway. continue; } if (!reposInRunfilesPaths.contains(mappingEntry.getValue().getName())) { // We only write entries for repos whose canonical names appear in runfiles paths. continue; } // The canonical name of the main repo is the empty string, which is not a valid name for a // directory, so the ""workspace name"" is used the name of the directory under the runfiles // tree for it. String [MASK] = mappingEntry.getValue().isMain() ? workspaceName : mappingEntry.getValue().getName(); writer.format( ""%s,%s,%s\n"", repoName.getName(), mappingEntry.getKey(), [MASK] ); } } } ","targetRepoDirectoryName " "// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.android; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.rules.android.AndroidDataConverter.JoinerType; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization; import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; import com.google.errorprone.annotations.CanIgnoreReturnValue; /** Builds up the spawn action for $android_rclass_generator. */ public class RClassGeneratorActionBuilder { @SerializationConstant @VisibleForSerialization static final AndroidDataConverter AAPT2_CONVERTER = AndroidDataConverter.builder(JoinerType.COLON_COMMA) .with(RClassGeneratorActionBuilder::depsToBusyboxArg) .build(); private ResourceDependencies dependencies; private Artifact classJarOut; private boolean finalFields = true; @CanIgnoreReturnValue public RClassGeneratorActionBuilder withDependencies(ResourceDependencies resourceDeps) { this.dependencies = resourceDeps; return this; } @CanIgnoreReturnValue public RClassGeneratorActionBuilder finalFields(boolean finalFields) { this.finalFields = finalFields; return this; } @CanIgnoreReturnValue public RClassGeneratorActionBuilder setClassJarOut(Artifact classJarOut) { this.classJarOut = classJarOut; return this; } public ResourceApk build(AndroidDataContext dataContext, ProcessedAndroidData data) { build(dataContext, data.getRTxt(), data.getManifest()); return data.withValidatedResources(classJarOut); } private void build( AndroidDataContext dataContext, Artifact rTxt, ProcessedAndroidManifest manifest) { BusyBoxActionBuilder builder = BusyBoxActionBuilder.create(dataContext, ""GENERATE_BINARY_R"") .addInput(""--primaryRTxt"", rTxt) .addInput(""--primaryManifest"", manifest.getManifest()) .maybeAddFlag(""--packageForR"", manifest.getPackage()) .addFlag(finalFields ? ""--finalFields"" : ""--nofinalFields""); if (dependencies != null && !dependencies.getResourceContainers().isEmpty()) { builder .addTransitiveFlagForEach( ""--library"", dependencies.getResourceContainers(), AAPT2_CONVERTER) .addTransitiveInputValues(dependencies.getTransitiveAapt2RTxt()) .addTransitiveInputValues(dependencies.getTransitiveManifests()) .addTransitiveInputValues(dependencies.getTransitiveAapt2ValidationArtifacts()); } builder .addOutput(""--classJarOutput"", classJarOut) .addLabelFlag(""--targetLabel"") .buildAndRegister(""Generating R Classes"", ""RClassGenerator""); } private static String depsToBusyboxArg(ValidatedAndroidResources [MASK] ) { Artifact rTxt = [MASK] .getAapt2RTxt(); return (rTxt != null ? rTxt.getExecPath() : """") + "","" + ( [MASK] .getManifest() != null ? [MASK] .getManifest().getExecPath() : """"); } } ","container " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.config.spring.beans.factory.annotation; import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.config.annotation.Method; import org.apache.dubbo.config.annotation.Reference; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.config.spring.ReferenceBean; import org.apache.dubbo.config.spring.api.DemoService; import org.apache.dubbo.config.spring.api.HelloService; import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.apache.dubbo.config.spring.reference.ReferenceBeanManager; import org.apache.dubbo.config.spring.util.DubboBeanUtils; import org.apache.dubbo.rpc.RpcContext; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.InjectionMetadata; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.net.InetSocketAddress; import java.util.Collection; import java.util.HashMap; import java.util.Map; import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD; /** * {@link ReferenceAnnotationBeanPostProcessor} Test * * @since 2.5.7 */ @EnableDubbo(scanBasePackages = ""org.apache.dubbo.config.spring.context.annotation.provider"") @ExtendWith(SpringExtension.class) @ContextConfiguration( classes = { ServiceAnnotationTestConfiguration.class, ReferenceAnnotationBeanPostProcessorTest.class, ReferenceAnnotationBeanPostProcessorTest.MyConfiguration.class, ReferenceAnnotationBeanPostProcessorTest.TestAspect.class }) @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) @TestPropertySource(properties = { ""consumer.version = ${demo.service.version}"", ""consumer.url = dubbo://127.0.0.1:12345?version=2.5.7"", }) @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) class ReferenceAnnotationBeanPostProcessorTest { @BeforeAll public static void setUp() { DubboBootstrap.reset(); } @AfterEach public void tearDown() { DubboBootstrap.reset(); } private static final String AOP_SUFFIX = ""(based on AOP)""; @Aspect @Component public static class TestAspect { @Around(""execution(* org.apache.dubbo.config.spring.context.annotation.provider.DemoServiceImpl.*(..))"") public Object aroundDemoService(ProceedingJoinPoint pjp) throws Throwable { return pjp.proceed() + AOP_SUFFIX + "" from "" + RpcContext.getContext().getLocalAddress(); } @Around(""execution(* org.apache.dubbo.config.spring.context.annotation.provider.*HelloService*.*(..))"") public Object aroundHelloService(ProceedingJoinPoint pjp) throws Throwable { return pjp.proceed() + AOP_SUFFIX + "" from "" + RpcContext.getContext().getLocalAddress(); } } @Autowired private ConfigurableApplicationContext context; @Autowired private HelloService defaultHelloService; @Autowired private HelloService helloServiceImpl; @Autowired private DemoService demoServiceImpl; // #5 ReferenceBean (Field Injection #3) @Reference(id = ""helloService"", methods = @Method(name = ""sayHello"", timeout = 100)) private HelloService helloService; // #6 ReferenceBean (Field Injection #4) @DubboReference(version = ""2"", url = ""dubbo://127.0.0.1:12345?version=2"", tag = ""demo_tag"") private HelloService helloService2; // #7 ReferenceBean (Field Injection #5) // The HelloService is the same as above service(#6 ReferenceBean (Field Injection #4)), helloService3 will be registered as an alias of helloService2 @DubboReference(version = ""2"", url = ""dubbo://127.0.0.1:12345?version=2"", tag = ""demo_tag"") private HelloService helloService3; // #8 ReferenceBean (Method Injection #3) @DubboReference(version = ""3"", url = ""dubbo://127.0.0.1:12345?version=2"", tag = ""demo_tag"") public void setHelloService2(HelloService helloService2) { // The helloService2 beanName is the same as above(#6 ReferenceBean (Field Injection #4)), and this will rename to helloService2#2 renamedHelloService2 = helloService2; } // #9 ReferenceBean (Method Injection #4) @DubboReference(version = ""4"", url = ""dubbo://127.0.0.1:12345?version=2"") public void setHelloService3(DemoService helloService3){ // The helloService3 beanName is the same as above(#7 ReferenceBean (Field Injection #5) is an alias), // The current beanName(helloService3) is not registered in the beanDefinitionMap, but it is already an alias. so this will rename to helloService3#2 this.renamedHelloService3 = helloService3; } private HelloService renamedHelloService2; private DemoService renamedHelloService3; @Test void testAop() throws Exception { Assertions.assertTrue(context.containsBean(""helloService"")); TestBean [MASK] = context.getBean(TestBean.class); Map demoServicesMap = context.getBeansOfType(DemoService.class); Assertions.assertNotNull( [MASK] .getDemoServiceFromAncestor()); Assertions.assertNotNull( [MASK] .getDemoServiceFromParent()); Assertions.assertNotNull( [MASK] .getDemoService()); Assertions.assertNotNull( [MASK] .myDemoService); Assertions.assertEquals(3, demoServicesMap.size()); Assertions.assertNotNull(context.getBean(""demoServiceImpl"")); Assertions.assertNotNull(context.getBean(""myDemoService"")); Assertions.assertNotNull(context.getBean(""demoService"")); Assertions.assertNotNull(context.getBean(""demoServiceFromParent"")); String callSuffix = AOP_SUFFIX + "" from ""+ InetSocketAddress.createUnresolved(NetUtils.getLocalHost(), 12345); String localCallSuffix = AOP_SUFFIX + "" from "" + InetSocketAddress.createUnresolved(""127.0.0.1"", 0); String directInvokeSuffix = AOP_SUFFIX + "" from null""; String defaultHelloServiceResult = ""Greeting, Mercy""; Assertions.assertEquals(defaultHelloServiceResult + directInvokeSuffix, defaultHelloService.sayHello(""Mercy"")); Assertions.assertEquals(defaultHelloServiceResult + localCallSuffix, helloService.sayHello(""Mercy"")); String helloServiceImplResult = ""Hello, Mercy""; Assertions.assertEquals(helloServiceImplResult + directInvokeSuffix, helloServiceImpl.sayHello(""Mercy"")); Assertions.assertEquals(helloServiceImplResult + callSuffix, helloService2.sayHello(""Mercy"")); String demoServiceResult = ""Hello,Mercy""; Assertions.assertEquals(demoServiceResult + directInvokeSuffix, demoServiceImpl.sayName(""Mercy"")); Assertions.assertEquals(demoServiceResult + callSuffix, [MASK] .getDemoServiceFromAncestor().sayName(""Mercy"")); Assertions.assertEquals(demoServiceResult + callSuffix, [MASK] .myDemoService.sayName(""Mercy"")); Assertions.assertEquals(demoServiceResult + callSuffix, [MASK] .getDemoService().sayName(""Mercy"")); Assertions.assertEquals(demoServiceResult + callSuffix, [MASK] .getDemoServiceFromParent().sayName(""Mercy"")); DemoService myDemoService = context.getBean(""myDemoService"", DemoService.class); Assertions.assertEquals(demoServiceResult + callSuffix, myDemoService.sayName(""Mercy"")); } @Test void testGetInjectedFieldReferenceBeanMap() { ReferenceAnnotationBeanPostProcessor beanPostProcessor = getReferenceAnnotationBeanPostProcessor(); Map> referenceBeanMap = beanPostProcessor.getInjectedFieldReferenceBeanMap(); Assertions.assertEquals(5, referenceBeanMap.size()); Map checkingFieldNames = new HashMap<>(); checkingFieldNames.put(""private org.apache.dubbo.config.spring.api.HelloService org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessorTest$MyConfiguration.helloService"", 0); checkingFieldNames.put(""private org.apache.dubbo.config.spring.api.HelloService org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessorTest.helloService"", 0); checkingFieldNames.put(""private org.apache.dubbo.config.spring.api.HelloService org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessorTest.helloService2"", 0); checkingFieldNames.put(""private org.apache.dubbo.config.spring.api.HelloService org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessorTest.helloService3"", 0); checkingFieldNames.put(""private org.apache.dubbo.config.spring.api.DemoService org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessorTest$ParentBean.demoServiceFromParent"", 0); for (Map.Entry> entry : referenceBeanMap.entrySet()) { InjectionMetadata.InjectedElement injectedElement = entry.getKey(); String member = injectedElement.getMember().toString(); Integer count = checkingFieldNames.get(member); Assertions.assertNotNull(count); checkingFieldNames.put(member, count + 1); } for (Map.Entry entry : checkingFieldNames.entrySet()) { Assertions.assertEquals(1, entry.getValue().intValue(), ""check field element failed: "" + entry.getKey()); } } private ReferenceAnnotationBeanPostProcessor getReferenceAnnotationBeanPostProcessor() { return DubboBeanUtils.getReferenceAnnotationBeanPostProcessor(context); } @Test void testGetInjectedMethodReferenceBeanMap() { ReferenceAnnotationBeanPostProcessor beanPostProcessor = getReferenceAnnotationBeanPostProcessor(); Map> referenceBeanMap = beanPostProcessor.getInjectedMethodReferenceBeanMap(); Assertions.assertEquals(4, referenceBeanMap.size()); Map checkingMethodNames = new HashMap<>(); checkingMethodNames.put(""setDemoServiceFromAncestor"", 0); checkingMethodNames.put(""setDemoService"", 0); checkingMethodNames.put(""setHelloService2"", 0); checkingMethodNames.put(""setHelloService3"", 0); for (Map.Entry> entry : referenceBeanMap.entrySet()) { InjectionMetadata.InjectedElement injectedElement = entry.getKey(); java.lang.reflect.Method method = (java.lang.reflect.Method) injectedElement.getMember(); Integer count = checkingMethodNames.get(method.getName()); Assertions.assertNotNull(count); Assertions.assertEquals(0, count.intValue()); checkingMethodNames.put(method.getName(), count + 1); } for (Map.Entry entry : checkingMethodNames.entrySet()) { Assertions.assertEquals(1, entry.getValue().intValue(), ""check method element failed: "" + entry.getKey()); } } @Test void testReferenceBeansMethodAnnotation() { ReferenceBeanManager referenceBeanManager = context.getBean(ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class); Collection referenceBeans = referenceBeanManager.getReferences(); Assertions.assertEquals(5, referenceBeans.size()); for (ReferenceBean referenceBean : referenceBeans) { ReferenceConfig referenceConfig = referenceBean.getReferenceConfig(); Assertions.assertNotNull(referenceConfig); Assertions.assertNotNull(referenceConfig.get()); } ReferenceBean helloServiceReferenceBean = referenceBeanManager.getById(""helloService""); Assertions.assertEquals(""helloService"", helloServiceReferenceBean.getId()); ReferenceConfig referenceConfig = helloServiceReferenceBean.getReferenceConfig(); Assertions.assertEquals(1, referenceConfig.getMethods().size()); ReferenceBean demoServiceFromParentReferenceBean = referenceBeanManager.getById(""demoServiceFromParent""); ReferenceBean demoServiceReferenceBean = referenceBeanManager.getById(""demoService""); Assertions.assertEquals(demoServiceFromParentReferenceBean.getKey(), demoServiceReferenceBean.getKey()); Assertions.assertEquals(demoServiceFromParentReferenceBean.getReferenceConfig(), demoServiceReferenceBean.getReferenceConfig()); Assertions.assertSame(demoServiceFromParentReferenceBean, demoServiceReferenceBean); ReferenceBean helloService2Bean = referenceBeanManager.getById(""helloService2""); Assertions.assertNotNull(helloService2Bean); Assertions.assertNotNull(helloService2Bean.getReferenceConfig()); Assertions.assertEquals(""demo_tag"", helloService2Bean.getReferenceConfig().getTag()); Assertions.assertNotNull(referenceBeanManager.getById(""myDemoService"")); Assertions.assertNotNull(referenceBeanManager.getById(""helloService2#2"")); } private static class AncestorBean { private DemoService demoServiceFromAncestor; @Autowired private ApplicationContext applicationContext; public DemoService getDemoServiceFromAncestor() { return demoServiceFromAncestor; } // #4 ReferenceBean (Method Injection #2) @Reference(id = ""myDemoService"", version = ""2.5.7"", url = ""dubbo://127.0.0.1:12345?version=2.5.7"") public void setDemoServiceFromAncestor(DemoService demoServiceFromAncestor) { this.demoServiceFromAncestor = demoServiceFromAncestor; } public ApplicationContext getApplicationContext() { return applicationContext; } } private static class ParentBean extends AncestorBean { // #2 ReferenceBean (Field Injection #2) @Reference(version = ""${consumer.version}"", url = ""${consumer.url}"") private DemoService demoServiceFromParent; public DemoService getDemoServiceFromParent() { return demoServiceFromParent; } } static class TestBean extends ParentBean { private DemoService demoService; @Autowired private DemoService myDemoService; @Autowired private ApplicationContext applicationContext; public DemoService getDemoService() { return demoService; } // #3 ReferenceBean (Method Injection #1) @Reference(version = ""2.5.7"", url = ""dubbo://127.0.0.1:12345?version=2.5.7"") public void setDemoService(DemoService demoService) { this.demoService = demoService; } } @Configuration static class MyConfiguration { // #1 ReferenceBean (Field Injection #1) @Reference(methods = @Method(name = ""sayHello"", timeout = 100)) private HelloService helloService; @Bean public TestBean [MASK] () { return new TestBean(); } } } ","testBean " "// Copyright 2021 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.packages; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import com.google.devtools.build.lib.vfs.ModifiedFileSet; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; import java.io.IOException; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@code native.glob} function. */ @RunWith(JUnit4.class) public class NativeGlobTest extends BuildViewTestCase { @Test public void glob_simple() throws Exception { makeFile(""test/starlark/file1.txt""); makeFile(""test/starlark/file2.txt""); makeFile(""test/starlark/file3.txt""); makeGlobFilegroup(""test/starlark/BUILD"", ""glob(['*'])""); assertAttrLabelList( ""//test/starlark:files"", ""srcs"", ImmutableList.of( ""//test/starlark:BUILD"", ""//test/starlark:file1.txt"", ""//test/starlark:file2.txt"", ""//test/starlark:file3.txt"")); } @Test public void glob_not_empty() throws Exception { makeGlobFilegroup(""test/starlark/BUILD"", ""glob(['foo*'], allow_empty=False)""); AssertionError e = assertThrows( AssertionError.class, () -> assertAttrLabelList(""//test/starlark:files"", ""srcs"", ImmutableList.of())); assertThat(e).hasMessageThat().contains(""allow_empty""); } @Test public void glob_simple_subdirs() throws Exception { makeFile(""test/starlark/sub/file1.txt""); makeFile(""test/starlark/sub2/file2.txt""); makeFile(""test/starlark/sub3/file3.txt""); makeGlobFilegroup(""test/starlark/BUILD"", ""glob(['**'])""); assertAttrLabelList( ""//test/starlark:files"", ""srcs"", ImmutableList.of( ""//test/starlark:BUILD"", ""//test/starlark:sub/file1.txt"", ""//test/starlark:sub2/file2.txt"", ""//test/starlark:sub3/file3.txt"")); } @Test public void glob_incremental() throws Exception { makeFile(""test/starlark/file1.txt""); makeGlobFilegroup(""test/starlark/BUILD"", ""glob(['**'])""); assertAttrLabelList( ""//test/starlark:files"", ""srcs"", ImmutableList.of(""//test/starlark:BUILD"", ""//test/starlark:file1.txt"")); scratch.file(""test/starlark/file2.txt""); scratch.file(""test/starlark/sub/subfile3.txt""); // Poke SkyFrame to tell it what changed. invalidateSkyFrameFiles( ""test/starlark"", ""test/starlark/file2.txt"", ""test/starlark/sub/subfile3.txt""); assertAttrLabelList( ""//test/starlark:files"", ""srcs"", ImmutableList.of( ""//test/starlark:BUILD"", ""//test/starlark:file1.txt"", ""//test/starlark:file2.txt"", ""//test/starlark:sub/subfile3.txt"")); } /** * Constructs a BUILD file containing a single rule with uses glob() to list files look for a rule * called :files in it. */ private void makeGlobFilegroup(String buildPath, String glob) throws IOException { scratch.file(buildPath, ""filegroup("", "" name = 'files',"", "" srcs = "" + glob, "")""); } private void assertAttrLabelList(String target, String [MASK] , List expectedLabels) throws Exception { ConfiguredTargetAndData cfgTarget = getConfiguredTargetAndData(target); assertThat(cfgTarget).isNotNull(); ImmutableList

* Provides a fail-safe registry service backed by cache file. The consumer/provider can still find each other when registry center crashed. *

* (SPI, Prototype, ThreadSafe) */ public abstract class AbstractRegistry implements Registry { // URL address separator, used in file cache, service provider URL separation private static final char URL_SEPARATOR = ' '; // URL address separated regular expression for parsing the service provider URL list in the file cache private static final String URL_SPLIT = ""\\s+""; // Max times to retry to save properties to local cache file private static final int MAX_RETRY_TIMES_SAVE_PROPERTIES = 3; // Default interval in millisecond for saving properties to local cache file private static final long DEFAULT_INTERVAL_SAVE_PROPERTIES = 500L; // Log output protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); // Local disk cache, where the special key value.registries records the list of registry centers, and the others are the list of notified service providers private final Properties properties = new Properties(); // File cache timing writing private final ScheduledExecutorService registryCacheExecutor; private final AtomicLong lastCacheChanged = new AtomicLong(); private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger(); private final Set registered = new ConcurrentHashSet<>(); private final ConcurrentMap> subscribed = new ConcurrentHashMap<>(); private final ConcurrentMap>> notified = new ConcurrentHashMap<>(); // Is it synchronized to save the file private boolean syncSaveFile; private URL registryUrl; // Local disk cache file private File file; private final boolean localCacheEnabled; protected RegistryManager registryManager; protected ApplicationModel applicationModel; private static final String CAUSE_MULTI_DUBBO_USING_SAME_FILE = ""multiple Dubbo instance are using the same file""; protected AbstractRegistry(URL url) { setUrl(url); registryManager = url.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class); localCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true); registryCacheExecutor = url.getOrDefaultFrameworkModel().getBeanFactory() .getBean(FrameworkExecutorRepository.class).getSharedScheduledExecutor(); if (localCacheEnabled) { // Start file save timer syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false); String defaultFilename = System.getProperty(USER_HOME) + DUBBO_REGISTRY + url.getApplication() + ""-"" + url.getAddress().replaceAll("":"", ""-"") + CACHE; String filename = url.getParameter(FILE_KEY, defaultFilename); File file = null; if (ConfigUtils.isNotEmpty(filename)) { file = new File(filename); if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) { if (!file.getParentFile().mkdirs()) { IllegalArgumentException illegalArgumentException = new IllegalArgumentException( ""Invalid registry cache file "" + file + "", cause: Failed to create directory "" + file.getParentFile() + ""!""); if (logger != null) { // 1-9 failed to read / save registry cache file. logger.error(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, ""cache directory inaccessible"", ""Try adjusting permission of the directory."", ""failed to create directory"", illegalArgumentException); } throw illegalArgumentException; } } } this.file = file; // When starting the subscription center, // we need to read the local cache file for future Registry fault tolerance processing. loadProperties(); notify(url.getBackupUrls()); } } protected static List filterEmpty(URL url, List urls) { if (CollectionUtils.isEmpty(urls)) { List result = new ArrayList<>(1); result.add(url.setProtocol(EMPTY_PROTOCOL)); return result; } return urls; } @Override public URL getUrl() { return registryUrl; } protected void setUrl(URL url) { if (url == null) { throw new IllegalArgumentException(""registry url == null""); } this.registryUrl = url; } public Set getRegistered() { return Collections.unmodifiableSet(registered); } public Map> getSubscribed() { return Collections.unmodifiableMap(subscribed); } public Map>> getNotified() { return Collections.unmodifiableMap(notified); } public File getCacheFile() { return file; } public Properties getCacheProperties() { return properties; } public AtomicLong getLastCacheChanged() { return lastCacheChanged; } public void doSaveProperties(long [MASK] ) { if ( [MASK] < lastCacheChanged.get()) { return; } if (file == null) { return; } // Save File lockfile = null; try { lockfile = new File(file.getAbsolutePath() + "".lock""); if (!lockfile.exists()) { lockfile.createNewFile(); } try (RandomAccessFile raf = new RandomAccessFile(lockfile, ""rw""); FileChannel channel = raf.getChannel()) { FileLock lock = channel.tryLock(); if (lock == null) { IOException ioException = new IOException(""Can not lock the registry cache file "" + file.getAbsolutePath() + "", "" + ""ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties""); // 1-9 failed to read / save registry cache file. logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, """", ""Adjust dubbo.registry.file."", ioException); throw ioException; } // Save try { if (!file.exists()) { file.createNewFile(); } Properties tmpProperties; if (syncSaveFile) { // When syncReport = true, properties.setProperty and properties.store are called from the same // thread(reportCacheExecutor), so deep copy is not required tmpProperties = properties; } else { // Using properties.setProperty and properties.store method will cause lock contention // under multi-threading, so deep copy a new container tmpProperties = new Properties(); Set> entries = properties.entrySet(); for (Map.Entry entry : entries) { tmpProperties.setProperty((String) entry.getKey(), (String) entry.getValue()); } } try (FileOutputStream outputFile = new FileOutputStream(file)) { tmpProperties.store(outputFile, ""Dubbo Registry Cache""); } } finally { lock.release(); } } } catch (Throwable e) { savePropertiesRetryTimes.incrementAndGet(); if (savePropertiesRetryTimes.get() >= MAX_RETRY_TIMES_SAVE_PROPERTIES) { if (e instanceof OverlappingFileLockException) { // fix #9341, ignore OverlappingFileLockException logger.info(""Failed to save registry cache file for file overlapping lock exception, file name "" + file.getName()); } else { // 1-9 failed to read / save registry cache file. logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, """", ""Failed to save registry cache file after retrying "" + MAX_RETRY_TIMES_SAVE_PROPERTIES + "" times, cause: "" + e.getMessage(), e); } savePropertiesRetryTimes.set(0); return; } if ( [MASK] < lastCacheChanged.get()) { savePropertiesRetryTimes.set(0); return; } else { registryCacheExecutor.schedule(() -> doSaveProperties(lastCacheChanged.incrementAndGet()), DEFAULT_INTERVAL_SAVE_PROPERTIES, TimeUnit.MILLISECONDS); } if (!(e instanceof OverlappingFileLockException)) { logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, ""However, the retrying count limit is not exceeded. Dubbo will still try."", ""Failed to save registry cache file, will retry, cause: "" + e.getMessage(), e); } } finally { if (lockfile != null) { if (!lockfile.delete()) { // 1-10 Failed to delete lock file. logger.warn(REGISTRY_FAILED_DELETE_LOCKFILE, """", """", String.format(""Failed to delete lock file [%s]"", lockfile.getName())); } } } } private void loadProperties() { if (file == null || !file.exists()) { return; } try (InputStream in = Files.newInputStream(file.toPath())) { properties.load(in); if (logger.isInfoEnabled()) { logger.info(""Loaded registry cache file "" + file); } } catch (IOException e) { // 1-9 failed to read / save registry cache file. logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, """", e.getMessage(), e); } catch (Throwable e) { // 1-9 failed to read / save registry cache file. logger.warn(REGISTRY_FAILED_READ_WRITE_CACHE_FILE, CAUSE_MULTI_DUBBO_USING_SAME_FILE, """", ""Failed to load registry cache file "" + file, e); } } public List getCacheUrls(URL url) { Map> categoryNotified = notified.get(url); if (CollectionUtils.isNotEmptyMap(categoryNotified)) { List urls = categoryNotified.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); return urls; } for (Map.Entry entry : properties.entrySet()) { String key = (String) entry.getKey(); String value = (String) entry.getValue(); if (StringUtils.isNotEmpty(key) && key.equals(url.getServiceKey()) && (Character.isLetter(key.charAt(0)) || key.charAt(0) == '_') && StringUtils.isNotEmpty(value)) { String[] arr = value.trim().split(URL_SPLIT); List urls = new ArrayList<>(); for (String u : arr) { urls.add(URL.valueOf(u)); } return urls; } } return null; } @Override public List lookup(URL url) { List result = new ArrayList<>(); Map> notifiedUrls = getNotified().get(url); if (CollectionUtils.isNotEmptyMap(notifiedUrls)) { for (List urls : notifiedUrls.values()) { for (URL u : urls) { if (!EMPTY_PROTOCOL.equals(u.getProtocol())) { result.add(u); } } } } else { final AtomicReference> reference = new AtomicReference<>(); NotifyListener listener = reference::set; subscribe(url, listener); // Subscribe logic guarantees the first notify to return List urls = reference.get(); if (CollectionUtils.isNotEmpty(urls)) { for (URL u : urls) { if (!EMPTY_PROTOCOL.equals(u.getProtocol())) { result.add(u); } } } } return result; } @Override public void register(URL url) { if (url == null) { throw new IllegalArgumentException(""register url == null""); } if (url.getPort() != 0) { if (logger.isInfoEnabled()) { logger.info(""Register: "" + url); } } registered.add(url); } @Override public void unregister(URL url) { if (url == null) { throw new IllegalArgumentException(""unregister url == null""); } if (url.getPort() != 0) { if (logger.isInfoEnabled()) { logger.info(""Unregister: "" + url); } } registered.remove(url); } @Override public void subscribe(URL url, NotifyListener listener) { if (url == null) { throw new IllegalArgumentException(""subscribe url == null""); } if (listener == null) { throw new IllegalArgumentException(""subscribe listener == null""); } if (logger.isInfoEnabled()) { logger.info(""Subscribe: "" + url); } Set listeners = subscribed.computeIfAbsent(url, n -> new ConcurrentHashSet<>()); listeners.add(listener); } @Override public void unsubscribe(URL url, NotifyListener listener) { if (url == null) { throw new IllegalArgumentException(""unsubscribe url == null""); } if (listener == null) { throw new IllegalArgumentException(""unsubscribe listener == null""); } if (logger.isInfoEnabled()) { logger.info(""Unsubscribe: "" + url); } Set listeners = subscribed.get(url); if (listeners != null) { listeners.remove(listener); } // do not forget remove notified notified.remove(url); } protected void recover() throws Exception { // register Set recoverRegistered = new HashSet<>(getRegistered()); if (!recoverRegistered.isEmpty()) { if (logger.isInfoEnabled()) { logger.info(""Recover register url "" + recoverRegistered); } for (URL url : recoverRegistered) { register(url); } } // subscribe Map> recoverSubscribed = new HashMap<>(getSubscribed()); if (!recoverSubscribed.isEmpty()) { if (logger.isInfoEnabled()) { logger.info(""Recover subscribe url "" + recoverSubscribed.keySet()); } for (Map.Entry> entry : recoverSubscribed.entrySet()) { URL url = entry.getKey(); for (NotifyListener listener : entry.getValue()) { subscribe(url, listener); } } } } protected void notify(List urls) { if (CollectionUtils.isEmpty(urls)) { return; } for (Map.Entry> entry : getSubscribed().entrySet()) { URL url = entry.getKey(); if (!UrlUtils.isMatch(url, urls.get(0))) { continue; } Set listeners = entry.getValue(); if (listeners != null) { for (NotifyListener listener : listeners) { try { notify(url, listener, filterEmpty(url, urls)); } catch (Throwable t) { // 1-7: Failed to notify registry event. logger.error(REGISTRY_FAILED_NOTIFY_EVENT, ""consumer is offline"", """", ""Failed to notify registry event, urls: "" + urls + "", cause: "" + t.getMessage(), t); } } } } } /** * Notify changes from the provider side. * * @param url consumer side url * @param listener listener * @param urls provider latest urls */ protected void notify(URL url, NotifyListener listener, List urls) { if (url == null) { throw new IllegalArgumentException(""notify url == null""); } if (listener == null) { throw new IllegalArgumentException(""notify listener == null""); } if ((CollectionUtils.isEmpty(urls)) && !ANY_VALUE.equals(url.getServiceInterface())) { // 1-4 Empty address. logger.warn(REGISTRY_EMPTY_ADDRESS, """", """", ""Ignore empty notify urls for subscribe url "" + url); return; } if (logger.isInfoEnabled()) { logger.info(""Notify urls for subscribe url "" + url + "", url size: "" + urls.size()); } // keep every provider's category. Map> result = new HashMap<>(); for (URL u : urls) { if (UrlUtils.isMatch(url, u)) { String category = u.getCategory(DEFAULT_CATEGORY); List categoryList = result.computeIfAbsent(category, k -> new ArrayList<>()); categoryList.add(u); } } if (result.size() == 0) { return; } Map> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>()); for (Map.Entry> entry : result.entrySet()) { String category = entry.getKey(); List categoryList = entry.getValue(); categoryNotified.put(category, categoryList); listener.notify(categoryList); // We will update our cache file after each notification. // When our Registry has a subscribed failure due to network jitter, we can return at least the existing cache URL. if (localCacheEnabled) { saveProperties(url); } } } private void saveProperties(URL url) { if (file == null) { return; } try { StringBuilder buf = new StringBuilder(); Map> categoryNotified = notified.get(url); if (categoryNotified != null) { for (List us : categoryNotified.values()) { for (URL u : us) { if (buf.length() > 0) { buf.append(URL_SEPARATOR); } buf.append(u.toFullString()); } } } properties.setProperty(url.getServiceKey(), buf.toString()); long [MASK] = lastCacheChanged.incrementAndGet(); if (syncSaveFile) { doSaveProperties( [MASK] ); } else { registryCacheExecutor.schedule(() -> doSaveProperties( [MASK] ), DEFAULT_INTERVAL_SAVE_PROPERTIES, TimeUnit.MILLISECONDS); } } catch (Throwable t) { logger.warn(INTERNAL_ERROR, ""unknown error in registry module"", """", t.getMessage(), t); } } @Override public void destroy() { if (logger.isInfoEnabled()) { logger.info(""Destroy registry:"" + getUrl()); } Set destroyRegistered = new HashSet<>(getRegistered()); if (!destroyRegistered.isEmpty()) { for (URL url : new HashSet<>(destroyRegistered)) { if (url.getParameter(DYNAMIC_KEY, true)) { try { unregister(url); if (logger.isInfoEnabled()) { logger.info(""Destroy unregister url "" + url); } } catch (Throwable t) { // 1-8: Failed to unregister / unsubscribe url on destroy. logger.warn(REGISTRY_FAILED_DESTROY_UNREGISTER_URL, """", """", ""Failed to unregister url "" + url + "" to registry "" + getUrl() + "" on destroy, cause: "" + t.getMessage(), t); } } } } Map> destroySubscribed = new HashMap<>(getSubscribed()); if (!destroySubscribed.isEmpty()) { for (Map.Entry> entry : destroySubscribed.entrySet()) { URL url = entry.getKey(); for (NotifyListener listener : entry.getValue()) { try { unsubscribe(url, listener); if (logger.isInfoEnabled()) { logger.info(""Destroy unsubscribe url "" + url); } } catch (Throwable t) { // 1-8: Failed to unregister / unsubscribe url on destroy. logger.warn(REGISTRY_FAILED_DESTROY_UNREGISTER_URL, """", """", ""Failed to unsubscribe url "" + url + "" to registry "" + getUrl() + "" on destroy, cause: "" + t.getMessage(), t); } } } } registryManager.removeDestroyedRegistry(this); } protected boolean acceptable(URL urlToRegistry) { String pattern = registryUrl.getParameter(ACCEPTS_KEY); if (StringUtils.isEmpty(pattern)) { return true; } String[] accepts = COMMA_SPLIT_PATTERN.split(pattern); Set allow = Arrays.stream(accepts).filter(p -> !p.startsWith(""-"")).collect(Collectors.toSet()); Set disAllow = Arrays.stream(accepts).filter(p -> p.startsWith(""-"")).map(p -> p.substring(1)).collect(Collectors.toSet()); if (CollectionUtils.isNotEmpty(allow)) { // allow first return allow.contains(urlToRegistry.getProtocol()); } else if (CollectionUtils.isNotEmpty(disAllow)) { // contains disAllow, deny return !disAllow.contains(urlToRegistry.getProtocol()); } else { // default allow return true; } } @Override public String toString() { return getUrl().toString(); } } ","version " "/* * Copyright 2015 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.buffer; import io.netty.util.internal.PlatformDependent; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ReadOnlyBufferException; import static io.netty.util.internal.MathUtil.isOutOfBounds; import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.PlatformDependent.BIG_ENDIAN_NATIVE_ORDER; /** * All operations get and set as {@link ByteOrder#BIG_ENDIAN}. */ final class UnsafeByteBufUtil { private static final boolean UNALIGNED = PlatformDependent.isUnaligned(); private static final byte ZERO = 0; static byte getByte(long address) { return PlatformDependent.getByte(address); } static short getShort(long address) { if (UNALIGNED) { short v = PlatformDependent.getShort(address); return BIG_ENDIAN_NATIVE_ORDER ? v : Short.reverseBytes(v); } return (short) (PlatformDependent.getByte(address) << 8 | PlatformDependent.getByte(address + 1) & 0xff); } static short getShortLE(long address) { if (UNALIGNED) { short v = PlatformDependent.getShort(address); return BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes(v) : v; } return (short) (PlatformDependent.getByte(address) & 0xff | PlatformDependent.getByte(address + 1) << 8); } static int getUnsignedMedium(long address) { if (UNALIGNED) { return (PlatformDependent.getByte(address) & 0xff) << 16 | (BIG_ENDIAN_NATIVE_ORDER ? PlatformDependent.getShort(address + 1) : Short.reverseBytes(PlatformDependent.getShort(address + 1))) & 0xffff; } return (PlatformDependent.getByte(address) & 0xff) << 16 | (PlatformDependent.getByte(address + 1) & 0xff) << 8 | PlatformDependent.getByte(address + 2) & 0xff; } static int getUnsignedMediumLE(long address) { if (UNALIGNED) { return (PlatformDependent.getByte(address) & 0xff) | ((BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes(PlatformDependent.getShort(address + 1)) : PlatformDependent.getShort(address + 1)) & 0xffff) << 8; } return PlatformDependent.getByte(address) & 0xff | (PlatformDependent.getByte(address + 1) & 0xff) << 8 | (PlatformDependent.getByte(address + 2) & 0xff) << 16; } static int getInt(long address) { if (UNALIGNED) { int v = PlatformDependent.getInt(address); return BIG_ENDIAN_NATIVE_ORDER ? v : Integer.reverseBytes(v); } return PlatformDependent.getByte(address) << 24 | (PlatformDependent.getByte(address + 1) & 0xff) << 16 | (PlatformDependent.getByte(address + 2) & 0xff) << 8 | PlatformDependent.getByte(address + 3) & 0xff; } static int getIntLE(long address) { if (UNALIGNED) { int v = PlatformDependent.getInt(address); return BIG_ENDIAN_NATIVE_ORDER ? Integer.reverseBytes(v) : v; } return PlatformDependent.getByte(address) & 0xff | (PlatformDependent.getByte(address + 1) & 0xff) << 8 | (PlatformDependent.getByte(address + 2) & 0xff) << 16 | PlatformDependent.getByte(address + 3) << 24; } static long getLong(long address) { if (UNALIGNED) { long v = PlatformDependent.getLong(address); return BIG_ENDIAN_NATIVE_ORDER ? v : Long.reverseBytes(v); } return ((long) PlatformDependent.getByte(address)) << 56 | (PlatformDependent.getByte(address + 1) & 0xffL) << 48 | (PlatformDependent.getByte(address + 2) & 0xffL) << 40 | (PlatformDependent.getByte(address + 3) & 0xffL) << 32 | (PlatformDependent.getByte(address + 4) & 0xffL) << 24 | (PlatformDependent.getByte(address + 5) & 0xffL) << 16 | (PlatformDependent.getByte(address + 6) & 0xffL) << 8 | (PlatformDependent.getByte(address + 7)) & 0xffL; } static long getLongLE(long address) { if (UNALIGNED) { long v = PlatformDependent.getLong(address); return BIG_ENDIAN_NATIVE_ORDER ? Long.reverseBytes(v) : v; } return (PlatformDependent.getByte(address)) & 0xffL | (PlatformDependent.getByte(address + 1) & 0xffL) << 8 | (PlatformDependent.getByte(address + 2) & 0xffL) << 16 | (PlatformDependent.getByte(address + 3) & 0xffL) << 24 | (PlatformDependent.getByte(address + 4) & 0xffL) << 32 | (PlatformDependent.getByte(address + 5) & 0xffL) << 40 | (PlatformDependent.getByte(address + 6) & 0xffL) << 48 | ((long) PlatformDependent.getByte(address + 7)) << 56; } static void setByte(long address, int value) { PlatformDependent.putByte(address, (byte) value); } static void setShort(long address, int value) { if (UNALIGNED) { PlatformDependent.putShort( address, BIG_ENDIAN_NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value)); } else { PlatformDependent.putByte(address, (byte) (value >>> 8)); PlatformDependent.putByte(address + 1, (byte) value); } } static void setShortLE(long address, int value) { if (UNALIGNED) { PlatformDependent.putShort( address, BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes((short) value) : (short) value); } else { PlatformDependent.putByte(address, (byte) value); PlatformDependent.putByte(address + 1, (byte) (value >>> 8)); } } static void setMedium(long address, int value) { PlatformDependent.putByte(address, (byte) (value >>> 16)); if (UNALIGNED) { PlatformDependent.putShort(address + 1, BIG_ENDIAN_NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value)); } else { PlatformDependent.putByte(address + 1, (byte) (value >>> 8)); PlatformDependent.putByte(address + 2, (byte) value); } } static void setMediumLE(long address, int value) { PlatformDependent.putByte(address, (byte) value); if (UNALIGNED) { PlatformDependent.putShort(address + 1, BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes((short) (value >>> 8)) : (short) (value >>> 8)); } else { PlatformDependent.putByte(address + 1, (byte) (value >>> 8)); PlatformDependent.putByte(address + 2, (byte) (value >>> 16)); } } static void setInt(long address, int value) { if (UNALIGNED) { PlatformDependent.putInt(address, BIG_ENDIAN_NATIVE_ORDER ? value : Integer.reverseBytes(value)); } else { PlatformDependent.putByte(address, (byte) (value >>> 24)); PlatformDependent.putByte(address + 1, (byte) (value >>> 16)); PlatformDependent.putByte(address + 2, (byte) (value >>> 8)); PlatformDependent.putByte(address + 3, (byte) value); } } static void setIntLE(long address, int value) { if (UNALIGNED) { PlatformDependent.putInt(address, BIG_ENDIAN_NATIVE_ORDER ? Integer.reverseBytes(value) : value); } else { PlatformDependent.putByte(address, (byte) value); PlatformDependent.putByte(address + 1, (byte) (value >>> 8)); PlatformDependent.putByte(address + 2, (byte) (value >>> 16)); PlatformDependent.putByte(address + 3, (byte) (value >>> 24)); } } static void setLong(long address, long value) { if (UNALIGNED) { PlatformDependent.putLong(address, BIG_ENDIAN_NATIVE_ORDER ? value : Long.reverseBytes(value)); } else { PlatformDependent.putByte(address, (byte) (value >>> 56)); PlatformDependent.putByte(address + 1, (byte) (value >>> 48)); PlatformDependent.putByte(address + 2, (byte) (value >>> 40)); PlatformDependent.putByte(address + 3, (byte) (value >>> 32)); PlatformDependent.putByte(address + 4, (byte) (value >>> 24)); PlatformDependent.putByte(address + 5, (byte) (value >>> 16)); PlatformDependent.putByte(address + 6, (byte) (value >>> 8)); PlatformDependent.putByte(address + 7, (byte) value); } } static void setLongLE(long address, long value) { if (UNALIGNED) { PlatformDependent.putLong(address, BIG_ENDIAN_NATIVE_ORDER ? Long.reverseBytes(value) : value); } else { PlatformDependent.putByte(address, (byte) value); PlatformDependent.putByte(address + 1, (byte) (value >>> 8)); PlatformDependent.putByte(address + 2, (byte) (value >>> 16)); PlatformDependent.putByte(address + 3, (byte) (value >>> 24)); PlatformDependent.putByte(address + 4, (byte) (value >>> 32)); PlatformDependent.putByte(address + 5, (byte) (value >>> 40)); PlatformDependent.putByte(address + 6, (byte) (value >>> 48)); PlatformDependent.putByte(address + 7, (byte) (value >>> 56)); } } static byte getByte(byte[] array, int index) { return PlatformDependent.getByte(array, index); } static short getShort(byte[] array, int index) { if (UNALIGNED) { short v = PlatformDependent.getShort(array, index); return BIG_ENDIAN_NATIVE_ORDER ? v : Short.reverseBytes(v); } return (short) (PlatformDependent.getByte(array, index) << 8 | PlatformDependent.getByte(array, index + 1) & 0xff); } static short getShortLE(byte[] array, int index) { if (UNALIGNED) { short v = PlatformDependent.getShort(array, index); return BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes(v) : v; } return (short) (PlatformDependent.getByte(array, index) & 0xff | PlatformDependent.getByte(array, index + 1) << 8); } static int getUnsignedMedium(byte[] array, int index) { if (UNALIGNED) { return (PlatformDependent.getByte(array, index) & 0xff) << 16 | (BIG_ENDIAN_NATIVE_ORDER ? PlatformDependent.getShort(array, index + 1) : Short.reverseBytes(PlatformDependent.getShort(array, index + 1))) & 0xffff; } return (PlatformDependent.getByte(array, index) & 0xff) << 16 | (PlatformDependent.getByte(array, index + 1) & 0xff) << 8 | PlatformDependent.getByte(array, index + 2) & 0xff; } static int getUnsignedMediumLE(byte[] array, int index) { if (UNALIGNED) { return (PlatformDependent.getByte(array, index) & 0xff) | ((BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes(PlatformDependent.getShort(array, index + 1)) : PlatformDependent.getShort(array, index + 1)) & 0xffff) << 8; } return PlatformDependent.getByte(array, index) & 0xff | (PlatformDependent.getByte(array, index + 1) & 0xff) << 8 | (PlatformDependent.getByte(array, index + 2) & 0xff) << 16; } static int getInt(byte[] array, int index) { if (UNALIGNED) { int v = PlatformDependent.getInt(array, index); return BIG_ENDIAN_NATIVE_ORDER ? v : Integer.reverseBytes(v); } return PlatformDependent.getByte(array, index) << 24 | (PlatformDependent.getByte(array, index + 1) & 0xff) << 16 | (PlatformDependent.getByte(array, index + 2) & 0xff) << 8 | PlatformDependent.getByte(array, index + 3) & 0xff; } static int getIntLE(byte[] array, int index) { if (UNALIGNED) { int v = PlatformDependent.getInt(array, index); return BIG_ENDIAN_NATIVE_ORDER ? Integer.reverseBytes(v) : v; } return PlatformDependent.getByte(array, index) & 0xff | (PlatformDependent.getByte(array, index + 1) & 0xff) << 8 | (PlatformDependent.getByte(array, index + 2) & 0xff) << 16 | PlatformDependent.getByte(array, index + 3) << 24; } static long getLong(byte[] array, int index) { if (UNALIGNED) { long v = PlatformDependent.getLong(array, index); return BIG_ENDIAN_NATIVE_ORDER ? v : Long.reverseBytes(v); } return ((long) PlatformDependent.getByte(array, index)) << 56 | (PlatformDependent.getByte(array, index + 1) & 0xffL) << 48 | (PlatformDependent.getByte(array, index + 2) & 0xffL) << 40 | (PlatformDependent.getByte(array, index + 3) & 0xffL) << 32 | (PlatformDependent.getByte(array, index + 4) & 0xffL) << 24 | (PlatformDependent.getByte(array, index + 5) & 0xffL) << 16 | (PlatformDependent.getByte(array, index + 6) & 0xffL) << 8 | (PlatformDependent.getByte(array, index + 7)) & 0xffL; } static long getLongLE(byte[] array, int index) { if (UNALIGNED) { long v = PlatformDependent.getLong(array, index); return BIG_ENDIAN_NATIVE_ORDER ? Long.reverseBytes(v) : v; } return PlatformDependent.getByte(array, index) & 0xffL | (PlatformDependent.getByte(array, index + 1) & 0xffL) << 8 | (PlatformDependent.getByte(array, index + 2) & 0xffL) << 16 | (PlatformDependent.getByte(array, index + 3) & 0xffL) << 24 | (PlatformDependent.getByte(array, index + 4) & 0xffL) << 32 | (PlatformDependent.getByte(array, index + 5) & 0xffL) << 40 | (PlatformDependent.getByte(array, index + 6) & 0xffL) << 48 | ((long) PlatformDependent.getByte(array, index + 7)) << 56; } static void setByte(byte[] array, int index, int value) { PlatformDependent.putByte(array, index, (byte) value); } static void setShort(byte[] array, int index, int value) { if (UNALIGNED) { PlatformDependent.putShort(array, index, BIG_ENDIAN_NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value)); } else { PlatformDependent.putByte(array, index, (byte) (value >>> 8)); PlatformDependent.putByte(array, index + 1, (byte) value); } } static void setShortLE(byte[] array, int index, int value) { if (UNALIGNED) { PlatformDependent.putShort(array, index, BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes((short) value) : (short) value); } else { PlatformDependent.putByte(array, index, (byte) value); PlatformDependent.putByte(array, index + 1, (byte) (value >>> 8)); } } static void setMedium(byte[] array, int index, int value) { PlatformDependent.putByte(array, index, (byte) (value >>> 16)); if (UNALIGNED) { PlatformDependent.putShort(array, index + 1, BIG_ENDIAN_NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value)); } else { PlatformDependent.putByte(array, index + 1, (byte) (value >>> 8)); PlatformDependent.putByte(array, index + 2, (byte) value); } } static void setMediumLE(byte[] array, int index, int value) { PlatformDependent.putByte(array, index, (byte) value); if (UNALIGNED) { PlatformDependent.putShort(array, index + 1, BIG_ENDIAN_NATIVE_ORDER ? Short.reverseBytes((short) (value >>> 8)) : (short) (value >>> 8)); } else { PlatformDependent.putByte(array, index + 1, (byte) (value >>> 8)); PlatformDependent.putByte(array, index + 2, (byte) (value >>> 16)); } } static void setInt(byte[] array, int index, int value) { if (UNALIGNED) { PlatformDependent.putInt(array, index, BIG_ENDIAN_NATIVE_ORDER ? value : Integer.reverseBytes(value)); } else { PlatformDependent.putByte(array, index, (byte) (value >>> 24)); PlatformDependent.putByte(array, index + 1, (byte) (value >>> 16)); PlatformDependent.putByte(array, index + 2, (byte) (value >>> 8)); PlatformDependent.putByte(array, index + 3, (byte) value); } } static void setIntLE(byte[] array, int index, int value) { if (UNALIGNED) { PlatformDependent.putInt(array, index, BIG_ENDIAN_NATIVE_ORDER ? Integer.reverseBytes(value) : value); } else { PlatformDependent.putByte(array, index, (byte) value); PlatformDependent.putByte(array, index + 1, (byte) (value >>> 8)); PlatformDependent.putByte(array, index + 2, (byte) (value >>> 16)); PlatformDependent.putByte(array, index + 3, (byte) (value >>> 24)); } } static void setLong(byte[] array, int index, long value) { if (UNALIGNED) { PlatformDependent.putLong(array, index, BIG_ENDIAN_NATIVE_ORDER ? value : Long.reverseBytes(value)); } else { PlatformDependent.putByte(array, index, (byte) (value >>> 56)); PlatformDependent.putByte(array, index + 1, (byte) (value >>> 48)); PlatformDependent.putByte(array, index + 2, (byte) (value >>> 40)); PlatformDependent.putByte(array, index + 3, (byte) (value >>> 32)); PlatformDependent.putByte(array, index + 4, (byte) (value >>> 24)); PlatformDependent.putByte(array, index + 5, (byte) (value >>> 16)); PlatformDependent.putByte(array, index + 6, (byte) (value >>> 8)); PlatformDependent.putByte(array, index + 7, (byte) value); } } static void setLongLE(byte[] array, int index, long value) { if (UNALIGNED) { PlatformDependent.putLong(array, index, BIG_ENDIAN_NATIVE_ORDER ? Long.reverseBytes(value) : value); } else { PlatformDependent.putByte(array, index, (byte) value); PlatformDependent.putByte(array, index + 1, (byte) (value >>> 8)); PlatformDependent.putByte(array, index + 2, (byte) (value >>> 16)); PlatformDependent.putByte(array, index + 3, (byte) (value >>> 24)); PlatformDependent.putByte(array, index + 4, (byte) (value >>> 32)); PlatformDependent.putByte(array, index + 5, (byte) (value >>> 40)); PlatformDependent.putByte(array, index + 6, (byte) (value >>> 48)); PlatformDependent.putByte(array, index + 7, (byte) (value >>> 56)); } } static void setZero(byte[] array, int index, int length) { if (length == 0) { return; } PlatformDependent.setMemory(array, index, length, ZERO); } static ByteBuf copy(AbstractByteBuf buf, long addr, int index, int length) { buf.checkIndex(index, length); ByteBuf copy = buf.alloc().directBuffer(length, buf.maxCapacity()); if (length != 0) { if (copy.hasMemoryAddress()) { PlatformDependent.copyMemory(addr, copy.memoryAddress(), length); copy.setIndex(0, length); } else { copy.writeBytes(buf, index, length); } } return copy; } static int setBytes(AbstractByteBuf buf, long addr, int index, InputStream in, int length) throws IOException { buf.checkIndex(index, length); ByteBuf tmpBuf = buf.alloc().heapBuffer(length); try { byte[] tmp = tmpBuf.array(); int offset = tmpBuf.arrayOffset(); int readBytes = in.read(tmp, offset, length); if (readBytes > 0) { PlatformDependent.copyMemory(tmp, offset, addr, readBytes); } return readBytes; } finally { tmpBuf.release(); } } static void getBytes(AbstractByteBuf buf, long addr, int index, ByteBuf dst, int dstIndex, int length) { buf.checkIndex(index, length); checkNotNull(dst, ""dst""); if (isOutOfBounds(dstIndex, length, dst.capacity())) { throw new IndexOutOfBoundsException(""dstIndex: "" + dstIndex); } if (dst.hasMemoryAddress()) { PlatformDependent.copyMemory(addr, dst.memoryAddress() + dstIndex, length); } else if (dst.hasArray()) { PlatformDependent.copyMemory(addr, dst.array(), dst.arrayOffset() + dstIndex, length); } else { dst.setBytes(dstIndex, buf, index, length); } } static void getBytes(AbstractByteBuf buf, long addr, int index, byte[] dst, int dstIndex, int length) { buf.checkIndex(index, length); checkNotNull(dst, ""dst""); if (isOutOfBounds(dstIndex, length, dst.length)) { throw new IndexOutOfBoundsException(""dstIndex: "" + dstIndex); } if (length != 0) { PlatformDependent.copyMemory(addr, dst, dstIndex, length); } } static void getBytes(AbstractByteBuf buf, long addr, int index, ByteBuffer dst) { buf.checkIndex(index, dst.remaining()); if (dst.remaining() == 0) { return; } if (dst.isDirect()) { if (dst.isReadOnly()) { // We need to check if dst is ready-only so we not write something in it by using Unsafe. throw new ReadOnlyBufferException(); } // Copy to direct memory long dstAddress = PlatformDependent.directBufferAddress(dst); PlatformDependent.copyMemory(addr, dstAddress + dst.position(), dst.remaining()); dst.position(dst.position() + dst.remaining()); } else if (dst.hasArray()) { // Copy to array PlatformDependent.copyMemory(addr, dst.array(), dst.arrayOffset() + dst.position(), dst.remaining()); dst.position(dst.position() + dst.remaining()); } else { dst.put(buf.nioBuffer()); } } static void setBytes(AbstractByteBuf buf, long addr, int index, ByteBuf src, int srcIndex, int length) { buf.checkIndex(index, length); checkNotNull(src, ""src""); if (isOutOfBounds(srcIndex, length, src.capacity())) { throw new IndexOutOfBoundsException(""srcIndex: "" + srcIndex); } if (length != 0) { if (src.hasMemoryAddress()) { PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, addr, length); } else if (src.hasArray()) { PlatformDependent.copyMemory(src.array(), src.arrayOffset() + srcIndex, addr, length); } else { src.getBytes(srcIndex, buf, index, length); } } } static void setBytes(AbstractByteBuf buf, long addr, int index, byte[] src, int srcIndex, int length) { buf.checkIndex(index, length); // we need to check not null for src as it may cause the JVM crash // See https://github.com/netty/netty/issues/10791 checkNotNull(src, ""src""); if (isOutOfBounds(srcIndex, length, src.length)) { throw new IndexOutOfBoundsException(""srcIndex: "" + srcIndex); } if (length != 0) { PlatformDependent.copyMemory(src, srcIndex, addr, length); } } static void setBytes(AbstractByteBuf buf, long addr, int index, ByteBuffer src) { final int length = src.remaining(); if (length == 0) { return; } if (src.isDirect()) { buf.checkIndex(index, length); // Copy from direct memory long srcAddress = PlatformDependent.directBufferAddress(src); PlatformDependent.copyMemory(srcAddress + src.position(), addr, length); src.position(src.position() + length); } else if (src.hasArray()) { buf.checkIndex(index, length); // Copy from array PlatformDependent.copyMemory(src.array(), src.arrayOffset() + src.position(), addr, length); src.position(src.position() + length); } else { if (length < 8) { setSingleBytes(buf, addr, index, src, length); } else { //no need to checkIndex: internalNioBuffer is already taking care of it assert buf.nioBufferCount() == 1; final ByteBuffer internalBuffer = buf.internalNioBuffer(index, length); internalBuffer.put(src); } } } private static void setSingleBytes(final AbstractByteBuf buf, final long addr, final int index, final ByteBuffer src, final int length) { buf.checkIndex(index, length); final int srcPosition = src.position(); final int srcLimit = src.limit(); long dstAddr = addr; for (int srcIndex = srcPosition; srcIndex < srcLimit; srcIndex++) { final byte value = src.get(srcIndex); PlatformDependent.putByte(dstAddr, value); dstAddr++; } src.position(srcLimit); } static void getBytes(AbstractByteBuf buf, long addr, int index, OutputStream out, int length) throws IOException { buf.checkIndex(index, length); if (length != 0) { int len = Math.min(length, ByteBufUtil.WRITE_CHUNK_SIZE); if (len <= ByteBufUtil.MAX_TL_ARRAY_LEN || !buf.alloc().isDirectBufferPooled()) { getBytes(addr, ByteBufUtil.threadLocalTempArray(len), 0, len, out, length); } else { // if direct buffers are pooled chances are good that heap buffers are pooled as well. ByteBuf tmpBuf = buf.alloc().heapBuffer(len); try { byte[] tmp = tmpBuf.array(); int offset = tmpBuf.arrayOffset(); getBytes(addr, tmp, offset, len, out, length); } finally { tmpBuf.release(); } } } } private static void getBytes(long inAddr, byte[] in, int inOffset, int inLen, OutputStream out, int outLen) throws IOException { do { int len = Math.min(inLen, outLen); PlatformDependent.copyMemory(inAddr, in, inOffset, len); out.write(in, inOffset, len); outLen -= len; inAddr += len; } while (outLen > 0); } static void setZero(long addr, int length) { if (length == 0) { return; } PlatformDependent.setMemory(addr, length, ZERO); } static UnpooledUnsafeDirectByteBuf newUnsafeDirectByteBuf( ByteBufAllocator alloc, int [MASK] , int maxCapacity) { if (PlatformDependent.useDirectBufferNoCleaner()) { return new UnpooledUnsafeNoCleanerDirectByteBuf(alloc, [MASK] , maxCapacity); } return new UnpooledUnsafeDirectByteBuf(alloc, [MASK] , maxCapacity); } private UnsafeByteBufUtil() { } } ","initialCapacity " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.optimize.peephole; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.*; import proguard.optimize.info.*; import java.util.Stack; /** * This AttributeVisitor inlines short methods or methods that are only invoked * once, in the code attributes that it visits. * * @see SuperInvocationMarker * @see BackwardBranchMarker * @see AccessMethodMarker * @see SideEffectClassMarker * @author Eric Lafortune */ public class MethodInliner extends SimplifiedVisitor implements AttributeVisitor, InstructionVisitor, ConstantVisitor, MemberVisitor, ExceptionInfoVisitor, LineNumberInfoVisitor { private static final int MAXIMUM_INLINED_CODE_LENGTH_JVM = Integer.parseInt(System.getProperty(""maximum.inlined.code.length"", ""8"")); private static final int MAXIMUM_INLINED_CODE_LENGTH_android= Integer.parseInt(System.getProperty(""maximum.inlined.code.length"", ""32"")); private static final int MAXIMUM_RESULTING_CODE_LENGTH_JSE = Integer.parseInt(System.getProperty(""maximum.resulting.code.length"", ""7000"")); private static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer.parseInt(System.getProperty(""maximum.resulting.code.length"", ""2000"")); static final int METHOD_DUMMY_START_LINE_NUMBER = 0; static final int INLINED_METHOD_END_LINE_NUMBER = -1; //* private static final boolean DEBUG = false; private static final boolean DEBUG_DETAILS = false; /*/ public static boolean DEBUG = System.getProperty(""mi"") != null; public static boolean DEBUG_DETAILS = System.getProperty(""mid"") != null; //*/ private final boolean microEdition; private final boolean android; private final boolean allowAccessModification; private final boolean inlineSingleInvocations; private final InstructionVisitor extraInlinedInvocationVisitor; private final CodeAttributeComposer [MASK] Composer = new CodeAttributeComposer(); private final MemberVisitor accessMethodMarker = new OptimizationInfoMemberFilter( new AllAttributeVisitor( new AllInstructionVisitor( new MultiInstructionVisitor( new SuperInvocationMarker(), new AccessMethodMarker() )))); private final AttributeVisitor methodInvocationMarker = new AllInstructionVisitor( new MethodInvocationMarker()); private final StackSizeComputer stackSizeComputer = new StackSizeComputer(); private ProgramClass targetClass; private ProgramMethod targetMethod; private ConstantAdder constantAdder; private ExceptionInfoAdder exceptionInfoAdder; private int estimatedResultingCodeLength; private boolean inlining; private Stack inliningMethods = new Stack(); private boolean emptyInvokingStack; private boolean coveredByCatchAllHandler; private int exceptionInfoCount; private int uninitializedObjectCount; private int variableOffset; private boolean inlined; private boolean inlinedAny; private boolean copiedLineNumbers; private String source; private int minimumLineNumberIndex; /** * Creates a new MethodInliner. * @param microEdition indicates whether the resulting code is * targeted at Java Micro Edition. * @param android indicates whether the resulting code is * targeted at the Dalvik VM. * @param allowAccessModification indicates whether the access modifiers of * classes and class members can be changed * in order to inline methods. * @param inlineSingleInvocations indicates whether the single invocations * should be inlined, or, alternatively, * short methods. */ public MethodInliner(boolean microEdition, boolean android, boolean allowAccessModification, boolean inlineSingleInvocations) { this(microEdition, android, allowAccessModification, inlineSingleInvocations, null); } /** * Creates a new MethodInliner. * @param microEdition indicates whether the resulting code is * targeted at Java Micro Edition. * @param android indicates whether the resulting code is * targeted at the androidVM. * @param allowAccessModification indicates whether the access modifiers of * classes and class members can be changed * in order to inline methods. * @param inlineSingleInvocations indicates whether the single invocations * should be inlined, or, alternatively, * short methods. * @param extraInlinedInvocationVisitor an optional extra visitor for all * inlined invocation instructions. */ public MethodInliner(boolean microEdition, boolean android, boolean allowAccessModification, boolean inlineSingleInvocations, InstructionVisitor extraInlinedInvocationVisitor) { this.microEdition = microEdition; this.android = android; this.allowAccessModification = allowAccessModification; this.inlineSingleInvocations = inlineSingleInvocations; this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor; } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute [MASK] ) { // TODO: Remove this when the method inliner has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, [MASK] ); } catch (RuntimeException ex) { System.err.println(""Unexpected error while inlining method:""); System.err.println("" Target class = [""+targetClass.getName()+""]""); System.err.println("" Target method = [""+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+""]""); if (inlining) { System.err.println("" Inlined class = [""+clazz.getName()+""]""); System.err.println("" Inlined method = [""+method.getName(clazz)+method.getDescriptor(clazz)+""]""); } System.err.println("" Exception = [""+ex.getClass().getName()+""] (""+ex.getMessage()+"")""); ex.printStackTrace(); System.err.println(""Not inlining this method""); if (DEBUG) { targetMethod.accept(targetClass, new ClassPrinter()); if (inlining) { method.accept(clazz, new ClassPrinter()); } throw ex; } } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute [MASK] ) { if (!inlining) { // [MASK] Composer.DEBUG = DEBUG = // clazz.getName().equals(""abc/Def"") && // method.getName(clazz).equals(""abc""); targetClass = (ProgramClass)clazz; targetMethod = (ProgramMethod)method; constantAdder = new ConstantAdder(targetClass); exceptionInfoAdder = new ExceptionInfoAdder(targetClass, [MASK] Composer); estimatedResultingCodeLength = [MASK] .u4codeLength; inliningMethods.clear(); uninitializedObjectCount = method.getName(clazz).equals(ClassConstants.METHOD_NAME_INIT) ? 1 : 0; inlinedAny = false; [MASK] Composer.reset(); stackSizeComputer.visitCodeAttribute(clazz, method, [MASK] ); // Append the body of the code. copyCode(clazz, method, [MASK] ); // Update the code attribute if any code has been inlined. if (inlinedAny) { [MASK] Composer.visitCodeAttribute(clazz, method, [MASK] ); // Update the super/private/package/protected accessing flags. method.accept(clazz, accessMethodMarker); } targetClass = null; targetMethod = null; constantAdder = null; } // Only inline the method if it is invoked once or if it is short. else if ((inlineSingleInvocations ? MethodInvocationMarker.getInvocationCount(method) == 1 : [MASK] .u4codeLength <= (android? MAXIMUM_INLINED_CODE_LENGTH_android: MAXIMUM_INLINED_CODE_LENGTH_JVM)) && estimatedResultingCodeLength + [MASK] .u4codeLength < (microEdition ? MAXIMUM_RESULTING_CODE_LENGTH_JME : MAXIMUM_RESULTING_CODE_LENGTH_JSE)) { if (DEBUG) { System.out.println(""MethodInliner: inlining [""+ clazz.getName()+"".""+method.getName(clazz)+method.getDescriptor(clazz)+""] in [""+ targetClass.getName()+"".""+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+""]""); } // Ignore the removal of the original method invocation, // the addition of the parameter setup, and // the modification of a few inlined instructions. estimatedResultingCodeLength += [MASK] .u4codeLength; // Append instructions to store the parameters. storeParameters(clazz, method); // Inline the body of the code. copyCode(clazz, method, [MASK] ); inlined = true; inlinedAny = true; } } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute [MASK] , LineNumberTableAttribute lineNumberTableAttribute) { // Remember the source if we're inlining a method. source = inlining ? clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + ':' + lineNumberTableAttribute.getLowestLineNumber() + ':' + lineNumberTableAttribute.getHighestLineNumber() : null; // Insert all line numbers, possibly partly before previously inserted // line numbers. lineNumberTableAttribute.lineNumbersAccept(clazz, method, [MASK] , this); copiedLineNumbers = true; } /** * Appends instructions to pop the parameters for the given method, storing * them in new local variables. */ private void storeParameters(Clazz clazz, Method method) { String descriptor = method.getDescriptor(clazz); boolean isStatic = (method.getAccessFlags() & ClassConstants.ACC_STATIC) != 0; // Count the number of parameters, taking into account their categories. int parameterSize = ClassUtil.internalMethodParameterSize(descriptor); int parameterOffset = isStatic ? 0 : 1; // Store the parameter types. String[] parameterTypes = new String[parameterSize]; InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++) { String parameterType = internalTypeEnumeration.nextType(); parameterTypes[parameterIndex] = parameterType; if (ClassUtil.internalTypeSize(parameterType) == 2) { parameterIndex++; } } [MASK] Composer.beginCodeFragment(parameterSize+1); // Go over the parameter types backward, storing the stack entries // in their corresponding variables. for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--) { String parameterType = parameterTypes[parameterIndex]; if (parameterType != null) { byte opcode; switch (parameterType.charAt(0)) { case ClassConstants.TYPE_BOOLEAN: case ClassConstants.TYPE_BYTE: case ClassConstants.TYPE_CHAR: case ClassConstants.TYPE_SHORT: case ClassConstants.TYPE_INT: opcode = InstructionConstants.OP_ISTORE; break; case ClassConstants.TYPE_LONG: opcode = InstructionConstants.OP_LSTORE; break; case ClassConstants.TYPE_FLOAT: opcode = InstructionConstants.OP_FSTORE; break; case ClassConstants.TYPE_DOUBLE: opcode = InstructionConstants.OP_DSTORE; break; default: opcode = InstructionConstants.OP_ASTORE; break; } [MASK] Composer.appendInstruction(parameterSize-parameterIndex-1, new VariableInstruction(opcode, variableOffset + parameterOffset + parameterIndex)); } } // Put the 'this' reference in variable 0 (plus offset). if (!isStatic) { [MASK] Composer.appendInstruction(parameterSize, new VariableInstruction(InstructionConstants.OP_ASTORE, variableOffset)); } [MASK] Composer.endCodeFragment(); } /** * Appends the code of the given code attribute. */ private void copyCode(Clazz clazz, Method method, CodeAttribute [MASK] ) { // The code may expand, due to expanding constant and variable // instructions. [MASK] Composer.beginCodeFragment( [MASK] .u4codeLength); // Copy the instructions. [MASK] .instructionsAccept(clazz, method, this); // Append a label just after the code. [MASK] Composer.appendLabel( [MASK] .u4codeLength); // Copy the exceptions. [MASK] .exceptionsAccept(clazz, method, exceptionInfoAdder); // Copy the line numbers. copiedLineNumbers = false; // The line numbers need to be inserted sequentially. minimumLineNumberIndex = 0; [MASK] .attributesAccept(clazz, method, this); // Make sure we at least have some entry at the start of the method. if (!copiedLineNumbers) { String source = inlining ? clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + "":0:0"" : null; minimumLineNumberIndex = [MASK] Composer.insertLineNumber(minimumLineNumberIndex, new ExtendedLineNumberInfo(0, METHOD_DUMMY_START_LINE_NUMBER, source)) + 1; } // Add a marker at the end of an inlined method. // The marker will be corrected in LineNumberLinearizer, // so it points to the line of the enclosing method. if (inlining) { String source = clazz.getName() + '.' + method.getName(clazz) + method.getDescriptor(clazz) + "":0:0""; minimumLineNumberIndex = [MASK] Composer.insertLineNumber(minimumLineNumberIndex, new ExtendedLineNumberInfo( [MASK] .u4codeLength, INLINED_METHOD_END_LINE_NUMBER, source)) + 1; } [MASK] Composer.endCodeFragment(); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute [MASK] , int offset, Instruction instruction) { [MASK] Composer.appendInstruction(offset, instruction); } public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute [MASK] , int offset, SimpleInstruction simpleInstruction) { // Are we inlining this instruction? if (inlining) { // Replace any return instructions by branches to the end of the code. switch (simpleInstruction.opcode) { case InstructionConstants.OP_IRETURN: case InstructionConstants.OP_LRETURN: case InstructionConstants.OP_FRETURN: case InstructionConstants.OP_DRETURN: case InstructionConstants.OP_ARETURN: case InstructionConstants.OP_RETURN: // Are we not at the last instruction? if (offset < [MASK] .u4codeLength-1) { // Replace the return instruction by a branch instruction. Instruction branchInstruction = new BranchInstruction(InstructionConstants.OP_GOTO_W, [MASK] .u4codeLength - offset); [MASK] Composer.appendInstruction(offset, branchInstruction); } else { // Just leave out the instruction, but put in a label, // for the sake of any other branch instructions. [MASK] Composer.appendLabel(offset); } return; } } [MASK] Composer.appendInstruction(offset, simpleInstruction); } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute [MASK] , int offset, VariableInstruction variableInstruction) { // Are we inlining this instruction? if (inlining) { // Update the variable index. variableInstruction.variableIndex += variableOffset; } [MASK] Composer.appendInstruction(offset, variableInstruction); } public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute [MASK] , int offset, ConstantInstruction constantInstruction) { // Is it a method invocation? switch (constantInstruction.opcode) { case InstructionConstants.OP_NEW: uninitializedObjectCount++; break; case InstructionConstants.OP_INVOKEVIRTUAL: case InstructionConstants.OP_INVOKESPECIAL: case InstructionConstants.OP_INVOKESTATIC: case InstructionConstants.OP_INVOKEINTERFACE: // See if we can inline it. inlined = false; // Append a label, in case the invocation will be inlined. [MASK] Composer.appendLabel(offset); emptyInvokingStack = !inlining && stackSizeComputer.isReachable(offset) && stackSizeComputer.getStackSizeAfter(offset) == 0; variableOffset += [MASK] .u2maxLocals; // Check if the method invocation is covered by a catch-all // exception handler. coveredByCatchAllHandler = false; exceptionInfoCount = 0; [MASK] .exceptionsAccept(clazz, method, offset, this); coveredByCatchAllHandler = exceptionInfoCount > 0 ? coveredByCatchAllHandler : true; clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); variableOffset -= [MASK] .u2maxLocals; // Was the method inlined? if (inlined) { if (extraInlinedInvocationVisitor != null) { extraInlinedInvocationVisitor.visitConstantInstruction(clazz, method, [MASK] , offset, constantInstruction); } // The invocation itself is no longer necessary. return; } break; } // Are we inlining this instruction? if (inlining) { // Make sure the constant is present in the constant pool of the // target class. constantInstruction.constantIndex = constantAdder.addConstant(clazz, constantInstruction.constantIndex); } [MASK] Composer.appendInstruction(offset, constantInstruction); } // Implementations for ConstantVisitor. public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant) { refConstant.referencedMemberAccept(this); } // Implementations for MemberVisitor. public void visitAnyMember(Clazz Clazz, Member member) {} public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { int accessFlags = programMethod.getAccessFlags(); if (DEBUG_DETAILS) { System.out.println(""MethodInliner: checking [""+ programClass.getName()+"".""+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+""] in [""+ targetClass.getName()+"".""+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+""]""); } if (// Don't inline methods that must be preserved. !KeepMarker.isKept(programMethod) && DEBUG(""Access?"") && // Only inline the method if it is private, static, or final. // This currently precludes default interface methods, because // they can't be final. (accessFlags & (ClassConstants.ACC_PRIVATE | ClassConstants.ACC_STATIC | ClassConstants.ACC_FINAL)) != 0 && DEBUG(""Synchronized?"") && // Only inline the method if it is not synchronized, etc. (accessFlags & (ClassConstants.ACC_SYNCHRONIZED | ClassConstants.ACC_NATIVE | ClassConstants.ACC_ABSTRACT)) == 0 && DEBUG(""Init?"") && // Don't inline an method, except in an method in the // same class. // (!programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT) || // (programClass.equals(targetClass) && // targetMethod.getName(targetClass).equals(ClassConstants.METHOD_NAME_INIT))) && !programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT) && DEBUG(""Self?"") && // Don't inline a method into itself. (!programMethod.equals(targetMethod) || !programClass.equals(targetClass)) && DEBUG(""Recurse?"") && // Only inline the method if it isn't recursing. !inliningMethods.contains(programMethod) && DEBUG(""Version?"") && // Only inline the method if its target class has at least the // same version number as the source class, in order to avoid // introducing incompatible constructs. targetClass.u4version >= programClass.u4version && DEBUG(""Super?"") && // Only inline the method if it doesn't invoke a super method or a // dynamic method, or if it is in the same class. (!SuperInvocationMarker.invokesSuperMethods(programMethod) && !DynamicInvocationMarker.invokesDynamically(programMethod) || programClass.equals(targetClass)) && DEBUG(""Branch?"") && // Only inline the method if it doesn't branch backward while there // are uninitialized objects. (!BackwardBranchMarker.branchesBackward(programMethod) || uninitializedObjectCount == 0) && DEBUG(""Access private?"") && // Only inline if the code access of the inlined method allows it. (allowAccessModification || ((!AccessMethodMarker.accessesPrivateCode(programMethod) || programClass.equals(targetClass)) && (!AccessMethodMarker.accessesPackageCode(programMethod) || ClassUtil.internalPackageName(programClass.getName()).equals( ClassUtil.internalPackageName(targetClass.getName()))))) && DEBUG(""Access private in subclass?"") && // Only inline a method from a superclass if it doesn't access // private code (with invokespecial), because we can't fix the // invocation. (test2172) [DGD-1258] (!AccessMethodMarker.accessesPrivateCode(programMethod) || programClass.equals(targetClass) || !targetClass.extendsOrImplements(programClass)) && DEBUG(""Access protected?"") && // Only inline code that accesses protected code into the same // class. (!AccessMethodMarker.accessesProtectedCode(programMethod) || programClass.equals(targetClass)) && DEBUG(""Synchronization?"") && // if the method to be inlined has a synchronized block only inline it into // the target method if its invocation is covered by a catchall handler or // none at all. This might happen if the target method has been obfuscated // with fake exception handlers. (!SynchronizedBlockMethodMarker.hasSynchronizedBlock(programMethod) || coveredByCatchAllHandler) && DEBUG(""Final fields?"") && // Methods assigning final fields cannot be inlined, at least on Android // this leads to VerifyErrors at runtime. // This should normally not happen anyways, but some tools modify/generate // bytecode that would lead to such situations, e.g. jacoco, see DGD-561. !FinalFieldAssignmentMarker.assignsFinalField(programMethod) && DEBUG(""Catch?"") && // Only inline the method if it doesn't catch exceptions, or if it // is invoked with an empty stack. (!CatchExceptionMarker.catchesExceptions(programMethod) || emptyInvokingStack) && DEBUG(""Stack?"") && // Only inline the method if it always returns with an empty // stack. !NonEmptyStackReturnMarker.returnsWithNonEmptyStack(programMethod) && DEBUG(""Side effects?"") && // Only inline the method if its related static initializers don't // have any side effects. !SideEffectClassChecker.mayHaveSideEffects(targetClass, programClass, programMethod)) { boolean oldInlining = inlining; inlining = true; inliningMethods.push(programMethod); // Inline the method body. programMethod.attributesAccept(programClass, this); // Update the optimization information of the target method. if (!KeepMarker.isKept(targetMethod)) { ProgramMethodOptimizationInfo.getProgramMethodOptimizationInfo(targetMethod) .merge(MethodOptimizationInfo.getMethodOptimizationInfo(programMethod)); } // Increment the invocation count of referenced methods again, // since they are now invoked from the inlined code too. programMethod.attributesAccept(programClass, methodInvocationMarker); inlining = oldInlining; inliningMethods.pop(); } else if (programMethod.getName(programClass).equals(ClassConstants.METHOD_NAME_INIT)) { uninitializedObjectCount--; } } // Implementations for LineNumberInfoVisitor. public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute [MASK] , LineNumberInfo lineNumberInfo) { try { String newSource = lineNumberInfo.getSource() != null ? lineNumberInfo.getSource() : source; LineNumberInfo newLineNumberInfo = newSource != null ? new ExtendedLineNumberInfo(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber, newSource) : new LineNumberInfo(lineNumberInfo.u2startPC, lineNumberInfo.u2lineNumber); minimumLineNumberIndex = [MASK] Composer.insertLineNumber(minimumLineNumberIndex, newLineNumberInfo) + 1; } catch (IllegalArgumentException e) { if (DEBUG) { System.err.println(""Invalid line number while inlining method:""); System.err.println("" Target class = [""+targetClass.getName()+""]""); System.err.println("" Target method = [""+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+""]""); if (inlining) { System.err.println("" Inlined class = [""+clazz.getName()+""]""); System.err.println("" Inlined method = [""+method.getName(clazz)+method.getDescriptor(clazz)+""]""); } System.err.println("" Exception = [""+e.getClass().getName()+""] (""+e.getMessage()+"")""); } } } /** * Returns true, while printing out the given debug message. */ private boolean DEBUG(String string) { if (DEBUG_DETAILS) { System.out.println("" ""+string); } return true; } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute [MASK] , ExceptionInfo exceptionInfo) { exceptionInfoCount++; coveredByCatchAllHandler |= exceptionInfo.u2catchType == 0; } } ","codeAttribute " "/* * Copyright (C) 2008 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.collect; import static com.google.common.collect.TableCollectionTest.DIVIDE_BY_2; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.TableCollectionTest.ColumnTests; import java.util.Map; @GwtCompatible public class TablesTransformValuesColumnTest extends ColumnTests { public TablesTransformValuesColumnTest() { super(false, false, true, true, false); } @Override Table makeTable() { Table [MASK] = HashBasedTable.create(); return Tables.transformValues( [MASK] , DIVIDE_BY_2); } @Override protected Map makePopulatedMap() { Table [MASK] = HashBasedTable.create(); [MASK] .put(""one"", 'a', 1); [MASK] .put(""two"", 'a', 2); [MASK] .put(""three"", 'a', 3); [MASK] .put(""four"", 'b', 4); return Tables.transformValues( [MASK] , DIVIDE_BY_2).column('a'); } } ","table " "/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject.internal; import static com.google.common.truth.Truth.assertThat; import static com.google.inject.internal.RealMapBinder.entryOfJakartaProviderOf; import static com.google.inject.internal.RealMapBinder.entryOfProviderOf; import static com.google.inject.internal.RealMapBinder.mapOf; import static com.google.inject.internal.RealMapBinder.mapOfCollectionOfJakartaProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfCollectionOfProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfJakartaProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfSetOfJakartaProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfSetOfProviderOf; import static com.google.inject.internal.RealMultibinder.collectionOfJakartaProvidersOf; import static com.google.inject.internal.RealMultibinder.collectionOfProvidersOf; import static com.google.inject.internal.RealMultibinder.setOf; import static com.google.inject.internal.RealMultibinder.setOfExtendsOf; import static com.google.inject.internal.SpiUtils.BindType.INSTANCE; import static com.google.inject.internal.SpiUtils.BindType.LINKED; import static com.google.inject.internal.SpiUtils.BindType.PROVIDER_INSTANCE; import static com.google.inject.internal.SpiUtils.BindType.PROVIDER_KEY; import static com.google.inject.internal.SpiUtils.VisitType.BOTH; import static com.google.inject.internal.SpiUtils.VisitType.INJECTOR; import static com.google.inject.internal.SpiUtils.VisitType.MODULE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.internal.Indexer.IndexedBinding; import com.google.inject.internal.RealMapBinder.ProviderMapEntry; import com.google.inject.multibindings.MapBinderBinding; import com.google.inject.multibindings.MultibinderBinding; import com.google.inject.multibindings.MultibindingsTargetVisitor; import com.google.inject.multibindings.OptionalBinderBinding; import com.google.inject.spi.DefaultBindingTargetVisitor; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; import com.google.inject.spi.InstanceBinding; import com.google.inject.spi.LinkedKeyBinding; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderKeyBinding; import com.google.inject.spi.ProviderLookup; import com.google.inject.util.Types; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Utilities for testing the Multibinder and MapBinder extension SPI. * * @author sameb@google.com (Sam Berlin) */ public class SpiUtils { /** The kind of test we should perform. A live Injector, a raw Elements (Module) test, or both. */ enum VisitType { INJECTOR, MODULE, BOTH } /** * Asserts that MapBinderBinding visitors for work correctly. * * @param The type of the binding * @param mapKey The key the map belongs to. * @param keyType the TypeLiteral of the key of the map * @param valueType the TypeLiteral of the value of the map * @param modules The modules that define the mapbindings * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) * test, or both. * @param [MASK] If duplicates are allowed. * @param expectedMapBindings The number of other mapbinders we expect to see. * @param results The kind of bindings contained in the mapbinder. */ static void assertMapVisitor( Key mapKey, TypeLiteral keyType, TypeLiteral valueType, Iterable modules, VisitType visitType, boolean [MASK] , int expectedMapBindings, MapResult... results) { if (visitType == null) { fail(""must test something""); } if (visitType == BOTH || visitType == INJECTOR) { mapInjectorTest( mapKey, keyType, valueType, modules, [MASK] , expectedMapBindings, results); } if (visitType == BOTH || visitType == MODULE) { mapModuleTest( mapKey, keyType, valueType, modules, [MASK] , expectedMapBindings, results); } } @SuppressWarnings(""unchecked"") private static void mapInjectorTest( Key mapKey, TypeLiteral keyType, TypeLiteral valueType, Iterable modules, boolean [MASK] , int expectedMapBindings, MapResult... results) { Injector injector = Guice.createInjector(modules); Visitor visitor = new Visitor<>(); Binding mapBinding = injector.getBinding(mapKey); MapBinderBinding mapbinder = (MapBinderBinding) mapBinding.acceptTargetVisitor(visitor); assertNotNull(mapbinder); assertEquals(mapKey, mapbinder.getMapKey()); assertEquals(keyType, mapbinder.getKeyTypeLiteral()); assertEquals(valueType, mapbinder.getValueTypeLiteral()); assertEquals( [MASK] , mapbinder.permitsDuplicates()); List>> entries = Lists.newArrayList(mapbinder.getEntries()); List> mapResults = Lists.newArrayList(results); assertEquals( ""wrong entries, expected: "" + mapResults + "", but was: "" + entries, mapResults.size(), entries.size()); for (MapResult result : mapResults) { Map.Entry> found = null; for (Map.Entry> entry : entries) { Object key = entry.getKey(); Binding value = entry.getValue(); if (key.equals(result.k) && matches(value, result.v)) { found = entry; break; } } if (found == null) { fail(""Could not find entry: "" + result + "" in remaining entries: "" + entries); } else { assertTrue( ""mapBinder doesn't contain: "" + found.getValue(), mapbinder.containsElement(found.getValue())); entries.remove(found); } } if (!entries.isEmpty()) { fail(""Found all entries of: "" + mapResults + "", but more were left over: "" + entries); } Key mapOfProvider = mapKey.ofType(mapOfProviderOf(keyType, valueType)); Key mapOfSetOfProvider = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType)); Key mapOfCollectionOfProvider = mapKey.ofType(mapOfCollectionOfProviderOf(keyType, valueType)); Key mapOfSet = mapKey.ofType(mapOf(keyType, setOf(valueType))); Key setOfEntry = mapKey.ofType(setOf(entryOfProviderOf(keyType, valueType))); Key collectionOfProvidersOfEntryOfProvider = mapKey.ofType(collectionOfProvidersOf(entryOfProviderOf(keyType, valueType))); Key setOfExtendsOfEntryOfProvider = mapKey.ofType(setOfExtendsOf(entryOfProviderOf(keyType, valueType))); Key mapOfKeyExtendsValueKey = mapKey.ofType(mapOf(keyType, TypeLiteral.get(Types.subtypeOf(valueType.getType())))); Key mapOfJakartaProvider = mapKey.ofType(mapOfJakartaProviderOf(keyType, valueType)); Key mapOfSetOfJakartaProvider = mapKey.ofType(mapOfSetOfJakartaProviderOf(keyType, valueType)); Key mapOfCollectionOfJakartaProvider = mapKey.ofType(mapOfCollectionOfJakartaProviderOf(keyType, valueType)); Key setOfJakartaEntry = mapKey.ofType(setOf(entryOfJakartaProviderOf(keyType, valueType))); Key collectionOfJakartaProvidersOfEntryOfProvider = mapKey.ofType(collectionOfJakartaProvidersOf(entryOfProviderOf(keyType, valueType))); assertEquals( ImmutableSet.of( mapOfJakartaProvider, mapOfProvider, mapOfSetOfProvider, mapOfSetOfJakartaProvider, mapOfCollectionOfProvider, mapOfCollectionOfJakartaProvider, mapOfSet, mapOfKeyExtendsValueKey), mapbinder.getAlternateMapKeys()); boolean entrySetMatch = false; boolean mapProviderMatch = false; boolean mapSetMatch = false; boolean mapSetProviderMatch = false; boolean mapCollectionProviderMatch = false; boolean collectionOfProvidersOfEntryOfProviderMatch = false; boolean setOfExtendsOfEntryOfProviderMatch = false; boolean mapOfKeyExtendsValueKeyMatch = false; boolean jakartaEntrySetMatch = false; boolean mapJakartaProviderMatch = false; boolean mapSetJakartaProviderMatch = false; boolean mapCollectionJakartaProviderMatch = false; boolean collectionOfJakartaProvidersOfEntryOfProviderMatch = false; List otherMapBindings = Lists.newArrayList(); List> otherMatches = Lists.newArrayList(); Multimap indexedEntries = MultimapBuilder.hashKeys().hashSetValues().build(); Indexer indexer = new Indexer(injector); int duplicates = 0; for (Binding b : injector.getAllBindings().values()) { boolean contains = mapbinder.containsElement(b); Object visited = ((Binding) b).acceptTargetVisitor(visitor); if (visited instanceof MapBinderBinding) { if (visited.equals(mapbinder)) { assertTrue(contains); } else { otherMapBindings.add(visited); } } else if (b.getKey().equals(mapOfProvider)) { assertTrue(contains); mapProviderMatch = true; } else if (b.getKey().equals(mapOfSet)) { assertTrue(contains); mapSetMatch = true; } else if (b.getKey().equals(mapOfSetOfProvider)) { assertTrue(contains); mapSetProviderMatch = true; } else if (b.getKey().equals(mapOfCollectionOfProvider)) { assertTrue(contains); mapCollectionProviderMatch = true; } else if (b.getKey().equals(setOfEntry)) { assertTrue(contains); entrySetMatch = true; // Validate that this binding is also a MultibinderBinding. assertThat(((Binding) b).acceptTargetVisitor(visitor)) .isInstanceOf(MultibinderBinding.class); } else if (b.getKey().equals(collectionOfProvidersOfEntryOfProvider)) { assertTrue(contains); collectionOfProvidersOfEntryOfProviderMatch = true; } else if (b.getKey().equals(setOfExtendsOfEntryOfProvider)) { assertTrue(contains); setOfExtendsOfEntryOfProviderMatch = true; } else if (b.getKey().equals(mapOfKeyExtendsValueKey)) { assertTrue(contains); mapOfKeyExtendsValueKeyMatch = true; } else if (b.getKey().equals(mapOfJakartaProvider)) { assertTrue(contains); mapJakartaProviderMatch = true; } else if (b.getKey().equals(mapOfSetOfJakartaProvider)) { assertTrue(contains); mapSetJakartaProviderMatch = true; } else if (b.getKey().equals(mapOfCollectionOfJakartaProvider)) { assertTrue(contains); mapCollectionJakartaProviderMatch = true; } else if (b.getKey().equals(setOfJakartaEntry)) { assertTrue(contains); jakartaEntrySetMatch = true; } else if (b.getKey().equals(collectionOfJakartaProvidersOfEntryOfProvider)) { assertTrue(contains); collectionOfJakartaProvidersOfEntryOfProviderMatch = true; } else if (contains) { if (b instanceof ProviderInstanceBinding) { ProviderInstanceBinding pib = (ProviderInstanceBinding) b; if (pib.getUserSuppliedProvider() instanceof ProviderMapEntry) { // weird casting required to workaround compilation issues with jdk6 ProviderMapEntry pme = (ProviderMapEntry) (Provider) pib.getUserSuppliedProvider(); Binding valueBinding = injector.getBinding(pme.getValueKey()); if (indexer.isIndexable(valueBinding) && !indexedEntries.put(pme.getKey(), valueBinding.acceptTargetVisitor(indexer))) { duplicates++; } } } otherMatches.add(b); } } int sizeOfOther = otherMatches.size(); if ( [MASK] ) { sizeOfOther--; // account for 1 duplicate binding } // Multiply by two because each has a value and Map.Entry. int expectedSize = 2 * (mapResults.size() + duplicates); assertEquals( ""Incorrect other matches:\n\t"" + Joiner.on(""\n\t"").join(otherMatches), expectedSize, sizeOfOther); assertTrue(entrySetMatch); assertTrue(mapProviderMatch); assertTrue(collectionOfProvidersOfEntryOfProviderMatch); assertTrue(setOfExtendsOfEntryOfProviderMatch); assertTrue(mapOfKeyExtendsValueKeyMatch); assertTrue(jakartaEntrySetMatch); assertTrue(mapJakartaProviderMatch); assertTrue(collectionOfJakartaProvidersOfEntryOfProviderMatch); assertEquals( [MASK] , mapSetMatch); assertEquals( [MASK] , mapSetProviderMatch); assertEquals( [MASK] , mapCollectionProviderMatch); assertEquals( [MASK] , mapSetJakartaProviderMatch); assertEquals( [MASK] , mapCollectionJakartaProviderMatch); assertEquals( ""other MapBindings found: "" + otherMapBindings, expectedMapBindings, otherMapBindings.size()); } @SuppressWarnings(""unchecked"") private static void mapModuleTest( Key mapKey, TypeLiteral keyType, TypeLiteral valueType, Iterable modules, boolean [MASK] , int expectedMapBindings, MapResult... results) { Set elements = ImmutableSet.copyOf(Elements.getElements(modules)); Visitor visitor = new Visitor<>(); MapBinderBinding mapbinder = null; Map, Binding> keyMap = Maps.newHashMap(); for (Element element : elements) { if (element instanceof Binding) { Binding binding = (Binding) element; keyMap.put(binding.getKey(), binding); if (binding.getKey().equals(mapKey)) { mapbinder = (MapBinderBinding) ((Binding) binding).acceptTargetVisitor(visitor); } } } assertNotNull(mapbinder); List> mapResults = Lists.newArrayList(results); // Make sure the entries returned from getEntries(elements) are correct. // Because getEntries() can return duplicates, make sure to continue searching, even // after we find one match. List>> entries = Lists.newArrayList(mapbinder.getEntries(elements)); for (MapResult result : mapResults) { List>> foundEntries = Lists.newArrayList(); for (Map.Entry> entry : entries) { Object key = entry.getKey(); Binding value = entry.getValue(); if (key.equals(result.k) && matches(value, result.v)) { assertTrue( ""mapBinder doesn't contain: "" + entry.getValue(), mapbinder.containsElement(entry.getValue())); foundEntries.add(entry); } } assertTrue( ""Could not find entry: "" + result + "" in remaining entries: "" + entries, !foundEntries.isEmpty()); entries.removeAll(foundEntries); } assertTrue( ""Found all entries of: "" + mapResults + "", but more were left over: "" + entries, entries.isEmpty()); assertEquals(mapKey, mapbinder.getMapKey()); assertEquals(keyType, mapbinder.getKeyTypeLiteral()); assertEquals(valueType, mapbinder.getValueTypeLiteral()); Key mapOfProvider = mapKey.ofType(mapOfProviderOf(keyType, valueType)); Key mapOfSetOfProvider = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType)); Key mapOfCollectionOfProvider = mapKey.ofType(mapOfCollectionOfProviderOf(keyType, valueType)); Key mapOfSet = mapKey.ofType(mapOf(keyType, setOf(valueType))); Key setOfEntry = mapKey.ofType(setOf(entryOfProviderOf(keyType, valueType))); Key collectionOfProvidersOfEntryOfProvider = mapKey.ofType(collectionOfProvidersOf(entryOfProviderOf(keyType, valueType))); Key setOfExtendsOfEntryOfProvider = mapKey.ofType(setOfExtendsOf(entryOfProviderOf(keyType, valueType))); Key mapOfKeyExtendsValueKey = mapKey.ofType(mapOf(keyType, TypeLiteral.get(Types.subtypeOf(valueType.getType())))); Key mapOfJakartaProvider = mapKey.ofType(mapOfJakartaProviderOf(keyType, valueType)); Key mapOfSetOfJakartaProvider = mapKey.ofType(mapOfSetOfJakartaProviderOf(keyType, valueType)); Key mapOfCollectionOfJakartaProvider = mapKey.ofType(mapOfCollectionOfJakartaProviderOf(keyType, valueType)); Key setOfJakartaEntry = mapKey.ofType(setOf(entryOfJakartaProviderOf(keyType, valueType))); Key collectionOfJakartaProvidersOfEntryOfProvider = mapKey.ofType(collectionOfJakartaProvidersOf(entryOfProviderOf(keyType, valueType))); assertEquals( ImmutableSet.of( mapOfProvider, mapOfJakartaProvider, mapOfSetOfProvider, mapOfSetOfJakartaProvider, mapOfCollectionOfProvider, mapOfCollectionOfJakartaProvider, mapOfSet, mapOfKeyExtendsValueKey), mapbinder.getAlternateMapKeys()); boolean entrySetMatch = false; boolean mapProviderMatch = false; boolean mapSetMatch = false; boolean mapSetProviderMatch = false; boolean mapCollectionProviderMatch = false; boolean collectionOfProvidersOfEntryOfProviderMatch = false; boolean setOfExtendsOfEntryOfProviderMatch = false; boolean mapOfKeyExtendsValueKeyMatch = false; boolean entrySetJakartaMatch = false; boolean mapJakartaProviderMatch = false; boolean mapSetJakartaProviderMatch = false; boolean mapCollectionJakartaProviderMatch = false; boolean collectionOfJakartaProvidersOfEntryOfProviderMatch = false; List otherMapBindings = Lists.newArrayList(); List otherMatches = Lists.newArrayList(); List otherElements = Lists.newArrayList(); Indexer indexer = new Indexer(null); Multimap indexedEntries = MultimapBuilder.hashKeys().hashSetValues().build(); int duplicates = 0; for (Element element : elements) { boolean contains = mapbinder.containsElement(element); if (!contains) { otherElements.add(element); } boolean matched = false; Key key = null; Binding b = null; if (element instanceof Binding) { b = (Binding) element; if (b instanceof ProviderInstanceBinding) { ProviderInstanceBinding pb = (ProviderInstanceBinding) b; if (pb.getUserSuppliedProvider() instanceof ProviderMapEntry) { // weird casting required to workaround jdk6 compilation problems ProviderMapEntry pme = (ProviderMapEntry) (Provider) pb.getUserSuppliedProvider(); Binding valueBinding = keyMap.get(pme.getValueKey()); if (indexer.isIndexable(valueBinding) && !indexedEntries.put(pme.getKey(), valueBinding.acceptTargetVisitor(indexer))) { duplicates++; } } } key = b.getKey(); Object visited = b.acceptTargetVisitor(visitor); if (visited instanceof MapBinderBinding) { matched = true; if (visited.equals(mapbinder)) { assertTrue(contains); } else { otherMapBindings.add(visited); } } } else if (element instanceof ProviderLookup) { key = ((ProviderLookup) element).getKey(); } if (!matched && key != null) { if (key.equals(mapOfProvider)) { matched = true; assertTrue(contains); mapProviderMatch = true; } else if (key.equals(mapOfSet)) { matched = true; assertTrue(contains); mapSetMatch = true; } else if (key.equals(mapOfSetOfProvider)) { matched = true; assertTrue(contains); mapSetProviderMatch = true; } else if (key.equals(mapOfCollectionOfProvider)) { matched = true; assertTrue(contains); mapCollectionProviderMatch = true; } else if (key.equals(setOfEntry)) { matched = true; assertTrue(contains); entrySetMatch = true; // Validate that this binding is also a MultibinderBinding. if (b != null) { assertTrue(b.acceptTargetVisitor(visitor) instanceof MultibinderBinding); } } else if (key.equals(collectionOfProvidersOfEntryOfProvider)) { matched = true; assertTrue(contains); collectionOfProvidersOfEntryOfProviderMatch = true; } else if (key.equals(setOfExtendsOfEntryOfProvider)) { matched = true; assertTrue(contains); setOfExtendsOfEntryOfProviderMatch = true; } else if (key.equals(mapOfKeyExtendsValueKey)) { matched = true; assertTrue(contains); mapOfKeyExtendsValueKeyMatch = true; } else if (key.equals(mapOfJakartaProvider)) { matched = true; assertTrue(contains); mapJakartaProviderMatch = true; } else if (key.equals(mapOfSetOfJakartaProvider)) { matched = true; assertTrue(contains); mapSetJakartaProviderMatch = true; } else if (key.equals(mapOfCollectionOfJakartaProvider)) { matched = true; assertTrue(contains); mapCollectionJakartaProviderMatch = true; } else if (key.equals(setOfJakartaEntry)) { matched = true; assertTrue(contains); entrySetJakartaMatch = true; } else if (key.equals(collectionOfJakartaProvidersOfEntryOfProvider)) { matched = true; assertTrue(contains); collectionOfJakartaProvidersOfEntryOfProviderMatch = true; } } if (!matched && contains) { otherMatches.add(element); } } int otherMatchesSize = otherMatches.size(); if ( [MASK] ) { otherMatchesSize--; // allow for 1 duplicate binding } // Multiply by 2 because each has a value, and Map.Entry int expectedSize = (mapResults.size() + duplicates) * 2; assertEquals( ""incorrect number of contains, leftover matches:\n"" + Joiner.on(""\n\t"").join(otherMatches), expectedSize, otherMatchesSize); assertTrue(entrySetMatch); assertTrue(mapProviderMatch); assertTrue(collectionOfProvidersOfEntryOfProviderMatch); assertTrue(setOfExtendsOfEntryOfProviderMatch); assertTrue(mapOfKeyExtendsValueKeyMatch); assertTrue(entrySetJakartaMatch); assertTrue(mapJakartaProviderMatch); assertTrue(collectionOfJakartaProvidersOfEntryOfProviderMatch); assertEquals( [MASK] , mapSetMatch); assertEquals( [MASK] , mapSetProviderMatch); assertEquals( [MASK] , mapCollectionProviderMatch); assertEquals( [MASK] , mapSetJakartaProviderMatch); assertEquals( [MASK] , mapCollectionJakartaProviderMatch); assertEquals( ""other MapBindings found: "" + otherMapBindings, expectedMapBindings, otherMapBindings.size()); // Validate that we can construct an injector out of the remaining bindings. Guice.createInjector(Elements.getModule(otherElements)); } /** * Asserts that MultibinderBinding visitors work correctly. * * @param The type of the binding * @param setKey The key the set belongs to. * @param elementType the TypeLiteral of the element * @param modules The modules that define the multibindings * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) * test, or both. * @param [MASK] If duplicates are allowed. * @param expectedMultibindings The number of other multibinders we expect to see. * @param results The kind of bindings contained in the multibinder. */ static void assertSetVisitor( Key> setKey, TypeLiteral elementType, Iterable modules, VisitType visitType, boolean [MASK] , int expectedMultibindings, BindResult... results) { if (visitType == null) { fail(""must test something""); } if (visitType == BOTH || visitType == INJECTOR) { setInjectorTest( setKey, elementType, modules, [MASK] , expectedMultibindings, results); } if (visitType == BOTH || visitType == MODULE) { setModuleTest(setKey, elementType, modules, [MASK] , expectedMultibindings, results); } } @SuppressWarnings(""unchecked"") private static void setInjectorTest( Key> setKey, TypeLiteral elementType, Iterable modules, boolean [MASK] , int otherMultibindings, BindResult... results) { Key collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType)); Key collectionOfJakartaProvidersKey = setKey.ofType(collectionOfJakartaProvidersOf(elementType)); Key setOfExtendsKey = setKey.ofType(setOfExtendsOf(elementType)); Injector injector = Guice.createInjector(modules); Visitor> visitor = new Visitor<>(); Binding> binding = injector.getBinding(setKey); MultibinderBinding> multibinder = (MultibinderBinding>) binding.acceptTargetVisitor(visitor); assertNotNull(multibinder); assertEquals(setKey, multibinder.getSetKey()); assertEquals(elementType, multibinder.getElementTypeLiteral()); assertEquals( [MASK] , multibinder.permitsDuplicates()); assertEquals( ImmutableSet.of( collectionOfProvidersKey, collectionOfJakartaProvidersKey, setOfExtendsKey), multibinder.getAlternateSetKeys()); List> elements = Lists.newArrayList(multibinder.getElements()); List> bindResults = Lists.newArrayList(results); assertEquals( ""wrong bind elements, expected: "" + bindResults + "", but was: "" + multibinder.getElements(), bindResults.size(), elements.size()); for (BindResult result : bindResults) { Binding found = null; for (Binding item : elements) { if (matches(item, result)) { found = item; break; } } if (found == null) { fail(""Could not find element: "" + result + "" in remaining elements: "" + elements); } else { elements.remove(found); } } if (!elements.isEmpty()) { fail(""Found all elements of: "" + bindResults + "", but more were left over: "" + elements); } Set> setOfElements = new HashSet<>(multibinder.getElements()); Set setOfIndexed = Sets.newHashSet(); Indexer indexer = new Indexer(injector); for (Binding oneBinding : setOfElements) { setOfIndexed.add(oneBinding.acceptTargetVisitor(indexer)); } List otherMultibinders = Lists.newArrayList(); List> otherContains = Lists.newArrayList(); boolean collectionOfProvidersMatch = false; boolean collectionOfJakartaProvidersMatch = false; boolean setOfExtendsKeyMatch = false; for (Binding b : injector.getAllBindings().values()) { boolean contains = multibinder.containsElement(b); Key key = b.getKey(); Object visited = ((Binding>) b).acceptTargetVisitor(visitor); if (visited != null) { if (visited.equals(multibinder)) { assertTrue(contains); } else { otherMultibinders.add(visited); } } else if (setOfElements.contains(b)) { assertTrue(contains); } else if (key.equals(collectionOfProvidersKey)) { assertTrue(contains); collectionOfProvidersMatch = true; } else if (key.equals(collectionOfJakartaProvidersKey)) { assertTrue(contains); collectionOfJakartaProvidersMatch = true; } else if (key.equals(setOfExtendsKey)) { assertTrue(contains); setOfExtendsKeyMatch = true; } else if (contains) { if (!indexer.isIndexable(b) || !setOfIndexed.contains(b.acceptTargetVisitor(indexer))) { otherContains.add(b); } } } assertTrue(collectionOfProvidersMatch); assertTrue(collectionOfJakartaProvidersMatch); assertTrue(setOfExtendsKeyMatch); if ( [MASK] ) { assertEquals(""contained more than it should: "" + otherContains, 1, otherContains.size()); } else { assertTrue(""contained more than it should: "" + otherContains, otherContains.isEmpty()); } assertEquals( ""other multibindings found: "" + otherMultibinders, otherMultibindings, otherMultibinders.size()); } @SuppressWarnings(""unchecked"") private static void setModuleTest( Key> setKey, TypeLiteral elementType, Iterable modules, boolean [MASK] , int otherMultibindings, BindResult... results) { Key collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType)); Key collectionOfJakartaProvidersKey = setKey.ofType(collectionOfJakartaProvidersOf(elementType)); Key setOfExtendsKey = setKey.ofType(setOfExtendsOf(elementType)); List> bindResults = Lists.newArrayList(results); List elements = Elements.getElements(modules); Visitor visitor = new Visitor<>(); MultibinderBinding> multibinder = null; for (Element element : elements) { if (element instanceof Binding && ((Binding) element).getKey().equals(setKey)) { multibinder = (MultibinderBinding>) ((Binding) element).acceptTargetVisitor(visitor); break; } } assertNotNull(multibinder); assertEquals(setKey, multibinder.getSetKey()); assertEquals(elementType, multibinder.getElementTypeLiteral()); assertEquals( ImmutableSet.of( collectionOfProvidersKey, collectionOfJakartaProvidersKey, setOfExtendsKey), multibinder.getAlternateSetKeys()); List otherMultibinders = Lists.newArrayList(); Set otherContains = new HashSet<>(); List otherElements = Lists.newArrayList(); int duplicates = 0; Set setOfIndexed = Sets.newHashSet(); Indexer indexer = new Indexer(null); boolean collectionOfProvidersMatch = false; boolean collectionOfJakartaProvidersMatch = false; boolean setOfExtendsMatch = false; for (Element element : elements) { boolean contains = multibinder.containsElement(element); if (!contains) { otherElements.add(element); } boolean matched = false; Key key = null; if (element instanceof Binding) { Binding binding = (Binding) element; if (indexer.isIndexable(binding) && !setOfIndexed.add((IndexedBinding) binding.acceptTargetVisitor(indexer))) { duplicates++; } key = binding.getKey(); Object visited = binding.acceptTargetVisitor(visitor); if (visited != null) { matched = true; if (visited.equals(multibinder)) { assertTrue(contains); } else { otherMultibinders.add(visited); } } } if (collectionOfProvidersKey.equals(key)) { assertTrue(contains); assertFalse(matched); collectionOfProvidersMatch = true; } else if (collectionOfJakartaProvidersKey.equals(key)) { assertTrue(contains); assertFalse(matched); collectionOfJakartaProvidersMatch = true; } else if (setOfExtendsKey.equals(key)) { assertTrue(contains); assertFalse(matched); setOfExtendsMatch = true; } else if (!matched && contains) { otherContains.add(element); } } if ( [MASK] ) { assertEquals( ""wrong contained elements: "" + otherContains, bindResults.size() + 1 + duplicates, otherContains.size()); } else { assertEquals( ""wrong contained elements: "" + otherContains, bindResults.size() + duplicates, otherContains.size()); } assertEquals( ""other multibindings found: "" + otherMultibinders, otherMultibindings, otherMultibinders.size()); assertTrue(collectionOfProvidersMatch); assertTrue(collectionOfJakartaProvidersMatch); assertTrue(setOfExtendsMatch); // Validate that we can construct an injector out of the remaining bindings. Guice.createInjector(Elements.getModule(otherElements)); } /** * Asserts that OptionalBinderBinding visitors for work correctly. * * @param The type of the binding * @param keyType The key OptionalBinder is binding * @param modules The modules that define the bindings * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) * test, or both. * @param expectedOtherOptionalBindings the # of other optional bindings we expect to see. * @param expectedDefault the expected default binding, or null if none * @param expectedActual the expected actual binding, or null if none * @param expectedUserLinkedActual the user binding that is the actual binding, used if neither * the default nor actual are set and a user binding existed for the type. */ static void assertOptionalVisitor( Key keyType, Iterable modules, VisitType visitType, int expectedOtherOptionalBindings, BindResult expectedDefault, BindResult expectedActual, BindResult expectedUserLinkedActual) { if (visitType == null) { fail(""must test something""); } // expect twice as many bindings because of java.util.Optional expectedOtherOptionalBindings *= 2; if (visitType == BOTH || visitType == INJECTOR) { optionalInjectorTest( keyType, modules, expectedOtherOptionalBindings, expectedDefault, expectedActual, expectedUserLinkedActual); } if (visitType == BOTH || visitType == MODULE) { optionalModuleTest( keyType, modules, expectedOtherOptionalBindings, expectedDefault, expectedActual, expectedUserLinkedActual); } } @SuppressWarnings({""unchecked"", ""rawtypes""}) private static void optionalInjectorTest( Key keyType, Iterable modules, int expectedOtherOptionalBindings, BindResult expectedDefault, BindResult expectedActual, BindResult expectedUserLinkedActual) { if (expectedUserLinkedActual != null) { assertNull(""cannot have actual if expecting user binding"", expectedActual); assertNull(""cannot have default if expecting user binding"", expectedDefault); } Key> optionalKey = keyType.ofType(RealOptionalBinder.optionalOf(keyType.getTypeLiteral())); Key javaOptionalKey = keyType.ofType(RealOptionalBinder.javaOptionalOf(keyType.getTypeLiteral())); Injector injector = Guice.createInjector(modules); Binding> optionalBinding = injector.getBinding(optionalKey); Visitor visitor = new Visitor(); OptionalBinderBinding> optionalBinder = (OptionalBinderBinding>) optionalBinding.acceptTargetVisitor(visitor); assertNotNull(optionalBinder); assertEquals(optionalKey, optionalBinder.getKey()); Binding javaOptionalBinding = injector.getBinding(javaOptionalKey); OptionalBinderBinding javaOptionalBinder = (OptionalBinderBinding) javaOptionalBinding.acceptTargetVisitor(visitor); assertNotNull(javaOptionalBinder); assertEquals(javaOptionalKey, javaOptionalBinder.getKey()); if (expectedDefault == null) { assertNull(""did not expect a default binding"", optionalBinder.getDefaultBinding()); assertNull(""did not expect a default binding"", javaOptionalBinder.getDefaultBinding()); } else { assertTrue( ""expectedDefault: "" + expectedDefault + "", actualDefault: "" + optionalBinder.getDefaultBinding(), matches(optionalBinder.getDefaultBinding(), expectedDefault)); assertTrue( ""expectedDefault: "" + expectedDefault + "", actualDefault: "" + javaOptionalBinder.getDefaultBinding(), matches(javaOptionalBinder.getDefaultBinding(), expectedDefault)); } if (expectedActual == null && expectedUserLinkedActual == null) { assertNull(optionalBinder.getActualBinding()); assertNull(javaOptionalBinder.getActualBinding()); } else if (expectedActual != null) { assertTrue( ""expectedActual: "" + expectedActual + "", actualActual: "" + optionalBinder.getActualBinding(), matches(optionalBinder.getActualBinding(), expectedActual)); assertTrue( ""expectedActual: "" + expectedActual + "", actualActual: "" + javaOptionalBinder.getActualBinding(), matches(javaOptionalBinder.getActualBinding(), expectedActual)); } else if (expectedUserLinkedActual != null) { assertTrue( ""expectedUserLinkedActual: "" + expectedUserLinkedActual + "", actualActual: "" + optionalBinder.getActualBinding(), matches(optionalBinder.getActualBinding(), expectedUserLinkedActual)); assertTrue( ""expectedUserLinkedActual: "" + expectedUserLinkedActual + "", actualActual: "" + javaOptionalBinder.getActualBinding(), matches(javaOptionalBinder.getActualBinding(), expectedUserLinkedActual)); } Key>> optionalJakartaProviderKey = keyType.ofType(RealOptionalBinder.optionalOfJakartaProvider(keyType.getTypeLiteral())); Key javaOptionalJakartaProviderKey = keyType.ofType(RealOptionalBinder.javaOptionalOfJakartaProvider(keyType.getTypeLiteral())); Key>> optionalProviderKey = keyType.ofType(RealOptionalBinder.optionalOfProvider(keyType.getTypeLiteral())); Key javaOptionalProviderKey = keyType.ofType(RealOptionalBinder.javaOptionalOfProvider(keyType.getTypeLiteral())); assertEquals( ImmutableSet.of( optionalProviderKey, optionalJakartaProviderKey), optionalBinder.getAlternateKeys()); assertEquals( ImmutableSet.of( javaOptionalProviderKey, javaOptionalJakartaProviderKey), javaOptionalBinder.getAlternateKeys()); boolean keyMatch = false; boolean optionalKeyMatch = false; boolean javaOptionalKeyMatch = false; boolean optionalJakartaProviderKeyMatch = false; boolean javaOptionalJakartaProviderKeyMatch = false; boolean optionalProviderKeyMatch = false; boolean javaOptionalProviderKeyMatch = false; boolean defaultMatch = false; boolean actualMatch = false; List otherOptionalBindings = Lists.newArrayList(); List otherMatches = Lists.newArrayList(); for (Binding b : injector.getAllBindings().values()) { boolean contains = optionalBinder.containsElement(b); assertEquals(contains, javaOptionalBinder.containsElement(b)); Object visited = b.acceptTargetVisitor(visitor); if (visited instanceof OptionalBinderBinding) { if (visited.equals(optionalBinder)) { assertTrue(contains); } else if (visited.equals(javaOptionalBinder)) { assertTrue(contains); } else { otherOptionalBindings.add(visited); } } if (b.getKey().equals(keyType)) { // keyType might match because a user bound it // (which is possible in a purely absent OptionalBinder) assertEquals(expectedDefault != null || expectedActual != null, contains); if (contains) { keyMatch = true; } } else if (b.getKey().equals(optionalKey)) { assertTrue(contains); optionalKeyMatch = true; } else if (b.getKey().equals(javaOptionalKey)) { assertTrue(contains); javaOptionalKeyMatch = true; } else if (b.getKey().equals(optionalJakartaProviderKey)) { assertTrue(contains); optionalJakartaProviderKeyMatch = true; } else if (b.getKey().equals(javaOptionalJakartaProviderKey)) { assertTrue(contains); javaOptionalJakartaProviderKeyMatch = true; } else if (b.getKey().equals(optionalProviderKey)) { assertTrue(contains); optionalProviderKeyMatch = true; } else if (b.getKey().equals(javaOptionalProviderKey)) { assertTrue(contains); javaOptionalProviderKeyMatch = true; } else if (expectedDefault != null && matches(b, expectedDefault)) { assertTrue(contains); defaultMatch = true; } else if (expectedActual != null && matches(b, expectedActual)) { assertTrue(contains); actualMatch = true; } else if (contains) { otherMatches.add(b); } } assertEquals(otherMatches.toString(), 0, otherMatches.size()); // only expect a keymatch if either default or actual are set assertEquals(expectedDefault != null || expectedActual != null, keyMatch); assertTrue(optionalKeyMatch); assertTrue(optionalJakartaProviderKeyMatch); assertTrue(optionalProviderKeyMatch); assertTrue(javaOptionalKeyMatch); assertTrue(javaOptionalJakartaProviderKeyMatch); assertTrue(javaOptionalProviderKeyMatch); assertEquals(expectedDefault != null, defaultMatch); assertEquals(expectedActual != null, actualMatch); assertEquals( ""other OptionalBindings found: "" + otherOptionalBindings, expectedOtherOptionalBindings, otherOptionalBindings.size()); } @SuppressWarnings({""unchecked"", ""rawtypes""}) private static void optionalModuleTest( Key keyType, Iterable modules, int expectedOtherOptionalBindings, BindResult expectedDefault, BindResult expectedActual, BindResult expectedUserLinkedActual) { if (expectedUserLinkedActual != null) { assertNull(""cannot have actual if expecting user binding"", expectedActual); assertNull(""cannot have default if expecting user binding"", expectedDefault); } Set elements = ImmutableSet.copyOf(Elements.getElements(modules)); Map, Binding> indexed = index(elements); Key> optionalKey = keyType.ofType(RealOptionalBinder.optionalOf(keyType.getTypeLiteral())); Key javaOptionalKey = keyType.ofType(RealOptionalBinder.javaOptionalOf(keyType.getTypeLiteral())); Visitor visitor = new Visitor(); Key defaultKey = null; Key actualKey = null; Binding optionalBinding = indexed.get(optionalKey); OptionalBinderBinding> optionalBinder = (OptionalBinderBinding>) optionalBinding.acceptTargetVisitor(visitor); Binding javaOptionalBinding = indexed.get(javaOptionalKey); OptionalBinderBinding javaOptionalBinder = (OptionalBinderBinding) javaOptionalBinding.acceptTargetVisitor(visitor); // Locate the defaultKey & actualKey for (Element element : elements) { if (optionalBinder.containsElement(element) && element instanceof Binding) { Binding binding = (Binding) element; if (isSourceEntry(binding, RealOptionalBinder.Source.DEFAULT)) { defaultKey = binding.getKey(); } else if (isSourceEntry(binding, RealOptionalBinder.Source.ACTUAL)) { actualKey = binding.getKey(); } } } assertNotNull(optionalBinder); assertNotNull(javaOptionalBinder); assertEquals(expectedDefault == null, defaultKey == null); assertEquals(expectedActual == null, actualKey == null); Key>> optionalJakartaProviderKey = keyType.ofType(RealOptionalBinder.optionalOfJakartaProvider(keyType.getTypeLiteral())); Key javaOptionalJakartaProviderKey = keyType.ofType(RealOptionalBinder.javaOptionalOfJakartaProvider(keyType.getTypeLiteral())); Key>> optionalProviderKey = keyType.ofType(RealOptionalBinder.optionalOfProvider(keyType.getTypeLiteral())); Key javaOptionalProviderKey = keyType.ofType(RealOptionalBinder.javaOptionalOfProvider(keyType.getTypeLiteral())); boolean keyMatch = false; boolean optionalKeyMatch = false; boolean javaOptionalKeyMatch = false; boolean optionalJakartaProviderKeyMatch = false; boolean javaOptionalJakartaProviderKeyMatch = false; boolean optionalProviderKeyMatch = false; boolean javaOptionalProviderKeyMatch = false; boolean defaultMatch = false; boolean actualMatch = false; List otherOptionalElements = Lists.newArrayList(); List otherContains = Lists.newArrayList(); List nonContainedElements = Lists.newArrayList(); for (Element element : elements) { boolean contains = optionalBinder.containsElement(element); assertEquals(contains, javaOptionalBinder.containsElement(element)); if (!contains) { nonContainedElements.add(element); } Key key = null; Binding b = null; if (element instanceof Binding) { b = (Binding) element; key = b.getKey(); Object visited = b.acceptTargetVisitor(visitor); if (visited instanceof OptionalBinderBinding) { if (visited.equals(optionalBinder)) { assertTrue(contains); } else if (visited.equals(javaOptionalBinder)) { assertTrue(contains); } else { otherOptionalElements.add(visited); } } } else if (element instanceof ProviderLookup) { key = ((ProviderLookup) element).getKey(); } if (key != null && key.equals(keyType)) { // keyType might match because a user bound it // (which is possible in a purely absent OptionalBinder) assertEquals(expectedDefault != null || expectedActual != null, contains); if (contains) { keyMatch = true; } } else if (key != null && key.equals(optionalKey)) { assertTrue(contains); optionalKeyMatch = true; } else if (key != null && key.equals(javaOptionalKey)) { assertTrue(contains); javaOptionalKeyMatch = true; } else if (key != null && key.equals(optionalJakartaProviderKey)) { assertTrue(contains); optionalJakartaProviderKeyMatch = true; } else if (key != null && key.equals(javaOptionalJakartaProviderKey)) { assertTrue(contains); javaOptionalJakartaProviderKeyMatch = true; } else if (key != null && key.equals(optionalProviderKey)) { assertTrue(contains); optionalProviderKeyMatch = true; } else if (key != null && key.equals(javaOptionalProviderKey)) { assertTrue(contains); javaOptionalProviderKeyMatch = true; } else if (key != null && key.equals(defaultKey)) { assertTrue(contains); if (b != null) { // otherwise it might just be a ProviderLookup into it assertTrue( ""expected: "" + expectedDefault + "", but was: "" + b, matches(b, expectedDefault)); defaultMatch = true; } } else if (key != null && key.equals(actualKey)) { assertTrue(contains); if (b != null) { // otherwise it might just be a ProviderLookup into it assertTrue(""expected: "" + expectedActual + "", but was: "" + b, matches(b, expectedActual)); actualMatch = true; } } else if (contains) { otherContains.add(element); } } // only expect a keymatch if either default or actual are set assertEquals(expectedDefault != null || expectedActual != null, keyMatch); assertTrue(optionalKeyMatch); assertTrue(optionalJakartaProviderKeyMatch); assertTrue(optionalProviderKeyMatch); assertTrue(javaOptionalKeyMatch); assertTrue(javaOptionalJakartaProviderKeyMatch); assertTrue(javaOptionalProviderKeyMatch); assertEquals(expectedDefault != null, defaultMatch); assertEquals(expectedActual != null, actualMatch); assertEquals(otherContains.toString(), 0, otherContains.size()); assertEquals( ""other OptionalBindings found: "" + otherOptionalElements, expectedOtherOptionalBindings, otherOptionalElements.size()); // Validate that we can construct an injector out of the remaining bindings. Guice.createInjector(Elements.getModule(nonContainedElements)); } private static boolean isSourceEntry(Binding b, RealOptionalBinder.Source type) { switch (type) { case ACTUAL: return b.getKey().getAnnotation() instanceof RealOptionalBinder.Actual; case DEFAULT: return b.getKey().getAnnotation() instanceof RealOptionalBinder.Default; default: throw new IllegalStateException(""invalid type: "" + type); } } /** Returns the subset of elements that have keys, indexed by them. */ private static Map, Binding> index(Iterable elements) { ImmutableMap.Builder, Binding> builder = ImmutableMap.builder(); for (Element element : elements) { if (element instanceof Binding) { builder.put(((Binding) element).getKey(), (Binding) element); } } return builder.buildOrThrow(); } static MapResult instance(K k, V v) { return new MapResult(k, new BindResult(INSTANCE, v, null)); } static MapResult linked(K k, Class clazz) { return new MapResult(k, new BindResult(LINKED, null, Key.get(clazz))); } static MapResult linked(K k, Key key) { return new MapResult(k, new BindResult(LINKED, null, key)); } static MapResult providerInstance(K k, V v) { return new MapResult(k, new BindResult(PROVIDER_INSTANCE, v, null)); } static class MapResult { private final K k; private final BindResult v; MapResult(K k, BindResult v) { this.k = k; this.v = v; } @Override public String toString() { return ""entry[key["" + k + ""],value["" + v + ""]]""; } } private static boolean matches(Binding item, BindResult result) { switch (result.type) { case INSTANCE: if (item instanceof InstanceBinding && ((InstanceBinding) item).getInstance().equals(result.instance)) { return true; } break; case LINKED: if (item instanceof LinkedKeyBinding && ((LinkedKeyBinding) item).getLinkedKey().equals(result.key)) { return true; } break; case PROVIDER_INSTANCE: if (item instanceof ProviderInstanceBinding && Objects.equal( ((ProviderInstanceBinding) item).getUserSuppliedProvider().get(), result.instance)) { return true; } break; case PROVIDER_KEY: if (item instanceof ProviderKeyBinding && ((ProviderKeyBinding) item).getProviderKey().equals(result.key)) { return true; } break; } return false; } static BindResult instance(T t) { return new BindResult(INSTANCE, t, null); } static BindResult linked(Class clazz) { return new BindResult(LINKED, null, Key.get(clazz)); } static BindResult linked(Key key) { return new BindResult(LINKED, null, key); } static BindResult providerInstance(T t) { return new BindResult(PROVIDER_INSTANCE, t, null); } static BindResult providerKey(Key key) { return new BindResult(PROVIDER_KEY, null, key); } /** The kind of binding. */ static enum BindType { INSTANCE, LINKED, PROVIDER_INSTANCE, PROVIDER_KEY } /** The result of the binding. */ static class BindResult { private final BindType type; private final Key key; private final T instance; private BindResult(BindType type, T instance, Key key) { this.type = type; this.instance = instance; this.key = key; } @Override public String toString() { switch (type) { case INSTANCE: return ""instance["" + instance + ""]""; case LINKED: return ""linkedKey["" + key + ""]""; case PROVIDER_INSTANCE: return ""providerInstance["" + instance + ""]""; case PROVIDER_KEY: return ""providerKey["" + key + ""]""; } return null; } } private static class Visitor extends DefaultBindingTargetVisitor implements MultibindingsTargetVisitor { @Override public Object visit(MultibinderBinding multibinding) { return multibinding; } @Override public Object visit(MapBinderBinding mapbinding) { return mapbinding; } @Override public Object visit(OptionalBinderBinding optionalbinding) { return optionalbinding; } } } ","allowDuplicates " "/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.samples.comparison.adapters; import android.content.Context; import android.view.ViewGroup; import com.androidquery.AQuery; import com.facebook.samples.comparison.holders.AQueryHolder; import com.facebook.samples.comparison.instrumentation.InstrumentedImageView; import com.facebook.samples.comparison.instrumentation.PerfListener; /** RecyclerView Adapter for Android Query */ public class AQueryAdapter extends ImageListAdapter { private AQuery mAQuery; public AQueryAdapter(Context context, PerfListener perfListener) { super(context, perfListener); mAQuery = new AQuery(context); } @Override public AQueryHolder onCreateViewHolder(ViewGroup parent, int [MASK] ) { final InstrumentedImageView instrImageView = new InstrumentedImageView(getContext()); return new AQueryHolder(getContext(), mAQuery, parent, instrImageView, getPerfListener()); } @Override public void shutDown() { for (int i = 0; i < getItemCount(); i++) { String uri = getItem(i); mAQuery.invalidate(uri); } super.clear(); } } ","viewType " "/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.ssl; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.internal.tcnative.Buffer; import io.netty.internal.tcnative.Library; import io.netty.internal.tcnative.SSL; import io.netty.internal.tcnative.SSLContext; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCounted; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.NativeLibraryLoader; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.SystemPropertyUtil; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.io.ByteArrayInputStream; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import static io.netty.handler.ssl.SslUtils.*; /** * Tells if {@code netty-tcnative} and its OpenSSL support * are available. */ public final class OpenSsl { private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class); private static final Throwable UNAVAILABILITY_CAUSE; static final List DEFAULT_CIPHERS; static final Set AVAILABLE_CIPHER_SUITES; private static final Set AVAILABLE_OPENSSL_CIPHER_SUITES; private static final Set AVAILABLE_JAVA_CIPHER_SUITES; private static final boolean SUPPORTS_KEYMANAGER_FACTORY; private static final boolean USE_KEYMANAGER_FACTORY; private static final boolean SUPPORTS_OCSP; private static final boolean TLSV13_SUPPORTED; private static final boolean IS_BORINGSSL; private static final Set CLIENT_DEFAULT_PROTOCOLS; private static final Set SERVER_DEFAULT_PROTOCOLS; static final Set SUPPORTED_PROTOCOLS_SET; static final String[] EXTRA_SUPPORTED_TLS_1_3_CIPHERS; static final String EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING; static final String[] NAMED_GROUPS; static final boolean JAVAX_CERTIFICATE_CREATION_SUPPORTED; // Use default that is supported in java 11 and earlier and also in OpenSSL / BoringSSL. // See https://github.com/netty/netty-tcnative/issues/567 // See https://www.java.com/en/configure_crypto.html for ordering private static final String[] DEFAULT_NAMED_GROUPS = { ""x25519"", ""secp256r1"", ""secp384r1"", ""secp521r1"" }; // self-signed certificate for netty.io and the matching private-key private static final String CERT = ""-----BEGIN CERTIFICATE-----\n"" + ""MIICrjCCAZagAwIBAgIIdSvQPv1QAZQwDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAxMLZXhhbXBs\n"" + ""ZS5jb20wIBcNMTgwNDA2MjIwNjU5WhgPOTk5OTEyMzEyMzU5NTlaMBYxFDASBgNVBAMTC2V4YW1w\n"" + ""bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAggbWsmDQ6zNzRZ5AW8E3eoGl\n"" + ""qWvOBDb5Fs1oBRrVQHuYmVAoaqwDzXYJ0LOwa293AgWEQ1jpcbZ2hpoYQzqEZBTLnFhMrhRFlH6K\n"" + ""bJND8Y33kZ/iSVBBDuGbdSbJShlM+4WwQ9IAso4MZ4vW3S1iv5fGGpLgbtXRmBf/RU8omN0Gijlv\n"" + ""WlLWHWijLN8xQtySFuBQ7ssW8RcKAary3pUm6UUQB+Co6lnfti0Tzag8PgjhAJq2Z3wbsGRnP2YS\n"" + ""vYoaK6qzmHXRYlp/PxrjBAZAmkLJs4YTm/XFF+fkeYx4i9zqHbyone5yerRibsHaXZWLnUL+rFoe\n"" + ""MdKvr0VS3sGmhQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQADQi441pKmXf9FvUV5EHU4v8nJT9Iq\n"" + ""yqwsKwXnr7AsUlDGHBD7jGrjAXnG5rGxuNKBQ35wRxJATKrUtyaquFUL6H8O6aGQehiFTk6zmPbe\n"" + ""12Gu44vqqTgIUxnv3JQJiox8S2hMxsSddpeCmSdvmalvD6WG4NthH6B9ZaBEiep1+0s0RUaBYn73\n"" + ""I7CCUaAtbjfR6pcJjrFk5ei7uwdQZFSJtkP2z8r7zfeANJddAKFlkaMWn7u+OIVuB4XPooWicObk\n"" + ""NAHFtP65bocUYnDpTVdiyvn8DdqyZ/EO8n1bBKBzuSLplk2msW4pdgaFgY7Vw/0wzcFXfUXmL1uy\n"" + ""G8sQD/wx\n"" + ""-----END CERTIFICATE-----""; private static final String KEY = ""-----BEGIN PRIVATE KEY-----\n"" + ""MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCCBtayYNDrM3NFnkBbwTd6gaWp\n"" + ""a84ENvkWzWgFGtVAe5iZUChqrAPNdgnQs7Brb3cCBYRDWOlxtnaGmhhDOoRkFMucWEyuFEWUfops\n"" + ""k0PxjfeRn+JJUEEO4Zt1JslKGUz7hbBD0gCyjgxni9bdLWK/l8YakuBu1dGYF/9FTyiY3QaKOW9a\n"" + ""UtYdaKMs3zFC3JIW4FDuyxbxFwoBqvLelSbpRRAH4KjqWd+2LRPNqDw+COEAmrZnfBuwZGc/ZhK9\n"" + ""ihorqrOYddFiWn8/GuMEBkCaQsmzhhOb9cUX5+R5jHiL3OodvKid7nJ6tGJuwdpdlYudQv6sWh4x\n"" + ""0q+vRVLewaaFAgMBAAECggEAP8tPJvFtTxhNJAkCloHz0D0vpDHqQBMgntlkgayqmBqLwhyb18pR\n"" + ""i0qwgh7HHc7wWqOOQuSqlEnrWRrdcI6TSe8R/sErzfTQNoznKWIPYcI/hskk4sdnQ//Yn9/Jvnsv\n"" + ""U/BBjOTJxtD+sQbhAl80JcA3R+5sArURQkfzzHOL/YMqzAsn5hTzp7HZCxUqBk3KaHRxV7NefeOE\n"" + ""xlZuWSmxYWfbFIs4kx19/1t7h8CHQWezw+G60G2VBtSBBxDnhBWvqG6R/wpzJ3nEhPLLY9T+XIHe\n"" + ""ipzdMOOOUZorfIg7M+pyYPji+ZIZxIpY5OjrOzXHciAjRtr5Y7l99K1CG1LguQKBgQDrQfIMxxtZ\n"" + ""vxU/1cRmUV9l7pt5bjV5R6byXq178LxPKVYNjdZ840Q0/OpZEVqaT1xKVi35ohP1QfNjxPLlHD+K\n"" + ""iDAR9z6zkwjIrbwPCnb5kuXy4lpwPcmmmkva25fI7qlpHtbcuQdoBdCfr/KkKaUCMPyY89LCXgEw\n"" + ""5KTDj64UywKBgQCNfbO+eZLGzhiHhtNJurresCsIGWlInv322gL8CSfBMYl6eNfUTZvUDdFhPISL\n"" + ""UljKWzXDrjw0ujFSPR0XhUGtiq89H+HUTuPPYv25gVXO+HTgBFZEPl4PpA+BUsSVZy0NddneyqLk\n"" + ""42Wey9omY9Q8WsdNQS5cbUvy0uG6WFoX7wKBgQDZ1jpW8pa0x2bZsQsm4vo+3G5CRnZlUp+XlWt2\n"" + ""dDcp5dC0xD1zbs1dc0NcLeGDOTDv9FSl7hok42iHXXq8AygjEm/QcuwwQ1nC2HxmQP5holAiUs4D\n"" + ""WHM8PWs3wFYPzE459EBoKTxeaeP/uWAn+he8q7d5uWvSZlEcANs/6e77eQKBgD21Ar0hfFfj7mK8\n"" + ""9E0FeRZBsqK3omkfnhcYgZC11Xa2SgT1yvs2Va2n0RcdM5kncr3eBZav2GYOhhAdwyBM55XuE/sO\n"" + ""eokDVutNeuZ6d5fqV96TRaRBpvgfTvvRwxZ9hvKF4Vz+9wfn/JvCwANaKmegF6ejs7pvmF3whq2k\n"" + ""drZVAoGAX5YxQ5XMTD0QbMAl7/6qp6S58xNoVdfCkmkj1ZLKaHKIjS/benkKGlySVQVPexPfnkZx\n"" + ""p/Vv9yyphBoudiTBS9Uog66ueLYZqpgxlM/6OhYg86Gm3U2ycvMxYjBM1NFiyze21AqAhI+HX+Ot\n"" + ""mraV2/guSgDgZAhukRZzeQ2RucI=\n"" + ""-----END PRIVATE KEY-----""; static { Throwable cause = null; if (SystemPropertyUtil.getBoolean(""io.netty.handler.ssl.noOpenSsl"", false)) { cause = new UnsupportedOperationException( ""OpenSSL was explicit disabled with -Dio.netty.handler.ssl.noOpenSsl=true""); logger.debug( ""netty-tcnative explicit disabled; "" + OpenSslEngine.class.getSimpleName() + "" will be unavailable."", cause); } else { // Test if netty-tcnative is in the classpath first. try { Class.forName(""io.netty.internal.tcnative.SSLContext"", false, PlatformDependent.getClassLoader(OpenSsl.class)); } catch (ClassNotFoundException t) { cause = t; logger.debug( ""netty-tcnative not in the classpath; "" + OpenSslEngine.class.getSimpleName() + "" will be unavailable.""); } // If in the classpath, try to load the native library and initialize netty-tcnative. if (cause == null) { try { // The JNI library was not already loaded. Load it now. loadTcNative(); } catch (Throwable t) { cause = t; logger.debug( ""Failed to load netty-tcnative; "" + OpenSslEngine.class.getSimpleName() + "" will be unavailable, unless the "" + ""application has already loaded the symbols by some other means. "" + ""See https://netty.io/wiki/forked-tomcat-native.html for more information."", t); } try { String engine = SystemPropertyUtil.get(""io.netty.handler.ssl.openssl.engine"", null); if (engine == null) { logger.debug(""Initialize netty-tcnative using engine: 'default'""); } else { logger.debug(""Initialize netty-tcnative using engine: '{}'"", engine); } initializeTcNative(engine); // The library was initialized successfully. If loading the library failed above, // reset the cause now since it appears that the library was loaded by some other // means. cause = null; } catch (Throwable t) { if (cause == null) { cause = t; } logger.debug( ""Failed to initialize netty-tcnative; "" + OpenSslEngine.class.getSimpleName() + "" will be unavailable. "" + ""See https://netty.io/wiki/forked-tomcat-native.html for more information."", t); } } } UNAVAILABILITY_CAUSE = cause; CLIENT_DEFAULT_PROTOCOLS = protocols(""jdk.tls.client.protocols""); SERVER_DEFAULT_PROTOCOLS = protocols(""jdk.tls.server.protocols""); if (cause == null) { logger.debug(""netty-tcnative using native library: {}"", SSL.versionString()); final List defaultCiphers = new ArrayList(); final Set availableOpenSslCipherSuites = new LinkedHashSet(128); boolean supportsKeyManagerFactory = false; boolean useKeyManagerFactory = false; boolean tlsv13Supported = false; String[] namedGroups = DEFAULT_NAMED_GROUPS; String[] defaultConvertedNamedGroups = new String[namedGroups.length]; for (int i = 0; i < namedGroups.length; i++) { defaultConvertedNamedGroups[i] = GroupsConverter.toOpenSsl(namedGroups[i]); } IS_BORINGSSL = ""BoringSSL"".equals(versionString()); if (IS_BORINGSSL) { EXTRA_SUPPORTED_TLS_1_3_CIPHERS = new String [] { ""TLS_AES_128_GCM_SHA256"", ""TLS_AES_256_GCM_SHA384"" , ""TLS_CHACHA20_POLY1305_SHA256"" }; StringBuilder ciphersBuilder = new StringBuilder(128); for (String cipher: EXTRA_SUPPORTED_TLS_1_3_CIPHERS) { ciphersBuilder.append(cipher).append("":""); } ciphersBuilder.setLength(ciphersBuilder.length() - 1); EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING = ciphersBuilder.toString(); } else { EXTRA_SUPPORTED_TLS_1_3_CIPHERS = EmptyArrays.EMPTY_STRINGS; EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING = StringUtil.EMPTY_STRING; } try { final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); long certBio = 0; long keyBio = 0; long cert = 0; long key = 0; try { // As we delegate to the KeyManager / TrustManager of the JDK we need to ensure it can actually // handle TLSv13 as otherwise we may see runtime exceptions if (SslProvider.isTlsv13Supported(SslProvider.JDK)) { try { StringBuilder tlsv13Ciphers = new StringBuilder(); for (String cipher : TLSV13_CIPHERS) { String converted = CipherSuiteConverter.toOpenSsl(cipher, IS_BORINGSSL); if (converted != null) { tlsv13Ciphers.append(converted).append(':'); } } if (tlsv13Ciphers.length() == 0) { tlsv13Supported = false; } else { tlsv13Ciphers.setLength(tlsv13Ciphers.length() - 1); SSLContext.setCipherSuite(sslCtx, tlsv13Ciphers.toString(), true); tlsv13Supported = true; } } catch (Exception ignore) { tlsv13Supported = false; } } SSLContext.setCipherSuite(sslCtx, ""ALL"", false); final long ssl = SSL.newSSL(sslCtx, true); try { for (String c: SSL.getCiphers(ssl)) { // Filter out bad input. if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c) || // Filter out TLSv1.3 ciphers if not supported. !tlsv13Supported && isTLSv13Cipher(c)) { continue; } availableOpenSslCipherSuites.add(c); } if (IS_BORINGSSL) { // Currently BoringSSL does not include these when calling SSL.getCiphers() even when these // are supported. Collections.addAll(availableOpenSslCipherSuites, EXTRA_SUPPORTED_TLS_1_3_CIPHERS); Collections.addAll(availableOpenSslCipherSuites, ""AEAD-AES128-GCM-SHA256"", ""AEAD-AES256-GCM-SHA384"", ""AEAD-CHACHA20-POLY1305-SHA256""); } PemEncoded [MASK] = PemPrivateKey.valueOf(KEY.getBytes(CharsetUtil.US_ASCII)); try { // Let's check if we can set a callback, which may not work if the used OpenSSL version // is to old. SSLContext.setCertificateCallback(sslCtx, null); X509Certificate certificate = selfSignedCertificate(); certBio = ReferenceCountedOpenSslContext.toBIO(ByteBufAllocator.DEFAULT, certificate); cert = SSL.parseX509Chain(certBio); keyBio = ReferenceCountedOpenSslContext.toBIO( UnpooledByteBufAllocator.DEFAULT, [MASK] .retain()); key = SSL.parsePrivateKey(keyBio, null); SSL.setKeyMaterial(ssl, cert, key); supportsKeyManagerFactory = true; try { boolean propertySet = SystemPropertyUtil.contains( ""io.netty.handler.ssl.openssl.useKeyManagerFactory""); if (!IS_BORINGSSL) { useKeyManagerFactory = SystemPropertyUtil.getBoolean( ""io.netty.handler.ssl.openssl.useKeyManagerFactory"", true); if (propertySet) { logger.info(""System property "" + ""'io.netty.handler.ssl.openssl.useKeyManagerFactory'"" + "" is deprecated and so will be ignored in the future""); } } else { useKeyManagerFactory = true; if (propertySet) { logger.info(""System property "" + ""'io.netty.handler.ssl.openssl.useKeyManagerFactory'"" + "" is deprecated and will be ignored when using BoringSSL""); } } } catch (Throwable ignore) { logger.debug(""Failed to get useKeyManagerFactory system property.""); } } catch (Error ignore) { logger.debug(""KeyManagerFactory not supported.""); } finally { [MASK] .release(); } } finally { SSL.freeSSL(ssl); if (certBio != 0) { SSL.freeBIO(certBio); } if (keyBio != 0) { SSL.freeBIO(keyBio); } if (cert != 0) { SSL.freeX509Chain(cert); } if (key != 0) { SSL.freePrivateKey(key); } } String groups = SystemPropertyUtil.get(""jdk.tls.namedGroups"", null); if (groups != null) { String[] nGroups = groups.split("",""); Set supportedNamedGroups = new LinkedHashSet(nGroups.length); Set supportedConvertedNamedGroups = new LinkedHashSet(nGroups.length); Set unsupportedNamedGroups = new LinkedHashSet(); for (String namedGroup : nGroups) { String converted = GroupsConverter.toOpenSsl(namedGroup); if (SSLContext.setCurvesList(sslCtx, converted)) { supportedConvertedNamedGroups.add(converted); supportedNamedGroups.add(namedGroup); } else { unsupportedNamedGroups.add(namedGroup); } } if (supportedNamedGroups.isEmpty()) { namedGroups = defaultConvertedNamedGroups; logger.info(""All configured namedGroups are not supported: {}. Use default: {}."", Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)), Arrays.toString(DEFAULT_NAMED_GROUPS)); } else { String[] groupArray = supportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS); if (unsupportedNamedGroups.isEmpty()) { logger.info(""Using configured namedGroups -D 'jdk.tls.namedGroup': {} "", Arrays.toString(groupArray)); } else { logger.info(""Using supported configured namedGroups: {}. Unsupported namedGroups: {}. "", Arrays.toString(groupArray), Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS))); } namedGroups = supportedConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS); } } else { namedGroups = defaultConvertedNamedGroups; } } finally { SSLContext.free(sslCtx); } } catch (Exception e) { logger.warn(""Failed to get the list of available OpenSSL cipher suites."", e); } NAMED_GROUPS = namedGroups; AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.unmodifiableSet(availableOpenSslCipherSuites); final Set availableJavaCipherSuites = new LinkedHashSet( AVAILABLE_OPENSSL_CIPHER_SUITES.size() * 2); for (String cipher: AVAILABLE_OPENSSL_CIPHER_SUITES) { // Included converted but also openssl cipher name if (!isTLSv13Cipher(cipher)) { availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, ""TLS"")); availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, ""SSL"")); } else { // TLSv1.3 ciphers have the correct format. availableJavaCipherSuites.add(cipher); } } addIfSupported(availableJavaCipherSuites, defaultCiphers, DEFAULT_CIPHER_SUITES); addIfSupported(availableJavaCipherSuites, defaultCiphers, TLSV13_CIPHER_SUITES); // Also handle the extra supported ciphers as these will contain some more stuff on BoringSSL. addIfSupported(availableJavaCipherSuites, defaultCiphers, EXTRA_SUPPORTED_TLS_1_3_CIPHERS); useFallbackCiphersIfDefaultIsEmpty(defaultCiphers, availableJavaCipherSuites); DEFAULT_CIPHERS = Collections.unmodifiableList(defaultCiphers); AVAILABLE_JAVA_CIPHER_SUITES = Collections.unmodifiableSet(availableJavaCipherSuites); final Set availableCipherSuites = new LinkedHashSet( AVAILABLE_OPENSSL_CIPHER_SUITES.size() + AVAILABLE_JAVA_CIPHER_SUITES.size()); availableCipherSuites.addAll(AVAILABLE_OPENSSL_CIPHER_SUITES); availableCipherSuites.addAll(AVAILABLE_JAVA_CIPHER_SUITES); AVAILABLE_CIPHER_SUITES = availableCipherSuites; SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory; USE_KEYMANAGER_FACTORY = useKeyManagerFactory; Set protocols = new LinkedHashSet(6); // Seems like there is no way to explicitly disable SSLv2Hello in openssl so it is always enabled protocols.add(SslProtocols.SSL_v2_HELLO); if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV2, SSL.SSL_OP_NO_SSLv2)) { protocols.add(SslProtocols.SSL_v2); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV3, SSL.SSL_OP_NO_SSLv3)) { protocols.add(SslProtocols.SSL_v3); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1, SSL.SSL_OP_NO_TLSv1)) { protocols.add(SslProtocols.TLS_v1); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_1, SSL.SSL_OP_NO_TLSv1_1)) { protocols.add(SslProtocols.TLS_v1_1); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_OP_NO_TLSv1_2)) { protocols.add(SslProtocols.TLS_v1_2); } // This is only supported by java8u272 and later. if (tlsv13Supported && doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_3, SSL.SSL_OP_NO_TLSv1_3)) { protocols.add(SslProtocols.TLS_v1_3); TLSV13_SUPPORTED = true; } else { TLSV13_SUPPORTED = false; } SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols); SUPPORTS_OCSP = doesSupportOcsp(); if (logger.isDebugEnabled()) { logger.debug(""Supported protocols (OpenSSL): {} "", SUPPORTED_PROTOCOLS_SET); logger.debug(""Default cipher suites (OpenSSL): {}"", DEFAULT_CIPHERS); } // Check if we can create a javax.security.cert.X509Certificate from our cert. This might fail on // JDK17 and above. In this case we will later throw an UnsupportedOperationException if someone // tries to access these via SSLSession. See https://github.com/netty/netty/issues/13560. boolean javaxCertificateCreationSupported; try { javax.security.cert.X509Certificate.getInstance(CERT.getBytes(CharsetUtil.US_ASCII)); javaxCertificateCreationSupported = true; } catch (javax.security.cert.CertificateException ex) { javaxCertificateCreationSupported = false; } JAVAX_CERTIFICATE_CREATION_SUPPORTED = javaxCertificateCreationSupported; } else { DEFAULT_CIPHERS = Collections.emptyList(); AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet(); AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet(); AVAILABLE_CIPHER_SUITES = Collections.emptySet(); SUPPORTS_KEYMANAGER_FACTORY = false; USE_KEYMANAGER_FACTORY = false; SUPPORTED_PROTOCOLS_SET = Collections.emptySet(); SUPPORTS_OCSP = false; TLSV13_SUPPORTED = false; IS_BORINGSSL = false; EXTRA_SUPPORTED_TLS_1_3_CIPHERS = EmptyArrays.EMPTY_STRINGS; EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING = StringUtil.EMPTY_STRING; NAMED_GROUPS = DEFAULT_NAMED_GROUPS; JAVAX_CERTIFICATE_CREATION_SUPPORTED = false; } } static String checkTls13Ciphers(InternalLogger logger, String ciphers) { if (IS_BORINGSSL && !ciphers.isEmpty()) { assert EXTRA_SUPPORTED_TLS_1_3_CIPHERS.length > 0; Set boringsslTlsv13Ciphers = new HashSet(EXTRA_SUPPORTED_TLS_1_3_CIPHERS.length); Collections.addAll(boringsslTlsv13Ciphers, EXTRA_SUPPORTED_TLS_1_3_CIPHERS); boolean ciphersNotMatch = false; for (String cipher: ciphers.split("":"")) { if (boringsslTlsv13Ciphers.isEmpty()) { ciphersNotMatch = true; break; } if (!boringsslTlsv13Ciphers.remove(cipher) && !boringsslTlsv13Ciphers.remove(CipherSuiteConverter.toJava(cipher, ""TLS""))) { ciphersNotMatch = true; break; } } // Also check if there are ciphers left. ciphersNotMatch |= !boringsslTlsv13Ciphers.isEmpty(); if (ciphersNotMatch) { if (logger.isInfoEnabled()) { StringBuilder javaCiphers = new StringBuilder(128); for (String cipher : ciphers.split("":"")) { javaCiphers.append(CipherSuiteConverter.toJava(cipher, ""TLS"")).append("":""); } javaCiphers.setLength(javaCiphers.length() - 1); logger.info( ""BoringSSL doesn't allow to enable or disable TLSv1.3 ciphers explicitly."" + "" Provided TLSv1.3 ciphers: '{}', default TLSv1.3 ciphers that will be used: '{}'."", javaCiphers, EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING); } return EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING; } } return ciphers; } static boolean isSessionCacheSupported() { return version() >= 0x10100000L; } /** * Returns a self-signed {@link X509Certificate} for {@code netty.io}. */ static X509Certificate selfSignedCertificate() throws CertificateException { return (X509Certificate) SslContext.X509_CERT_FACTORY.generateCertificate( new ByteArrayInputStream(CERT.getBytes(CharsetUtil.US_ASCII)) ); } private static boolean doesSupportOcsp() { boolean supportsOcsp = false; if (version() >= 0x10002000L) { long sslCtx = -1; try { sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER); SSLContext.enableOcsp(sslCtx, false); supportsOcsp = true; } catch (Exception ignore) { // ignore } finally { if (sslCtx != -1) { SSLContext.free(sslCtx); } } } return supportsOcsp; } private static boolean doesSupportProtocol(int protocol, int opt) { if (opt == 0) { // If the opt is 0 the protocol is not supported. This is for example the case with BoringSSL and SSLv2. return false; } long sslCtx = -1; try { sslCtx = SSLContext.make(protocol, SSL.SSL_MODE_COMBINED); return true; } catch (Exception ignore) { return false; } finally { if (sslCtx != -1) { SSLContext.free(sslCtx); } } } /** * Returns {@code true} if and only if * {@code netty-tcnative} and its OpenSSL support * are available. */ public static boolean isAvailable() { return UNAVAILABILITY_CAUSE == null; } /** * Returns {@code true} if the used version of openssl supports * ALPN. * * @deprecated use {@link SslProvider#isAlpnSupported(SslProvider)} with {@link SslProvider#OPENSSL}. */ @Deprecated public static boolean isAlpnSupported() { return version() >= 0x10002000L; } /** * Returns {@code true} if the used version of OpenSSL supports OCSP stapling. */ public static boolean isOcspSupported() { return SUPPORTS_OCSP; } /** * Returns the version of the used available OpenSSL library or {@code -1} if {@link #isAvailable()} * returns {@code false}. */ public static int version() { return isAvailable() ? SSL.version() : -1; } /** * Returns the version string of the used available OpenSSL library or {@code null} if {@link #isAvailable()} * returns {@code false}. */ public static String versionString() { return isAvailable() ? SSL.versionString() : null; } /** * Ensure that {@code netty-tcnative} and * its OpenSSL support are available. * * @throws UnsatisfiedLinkError if unavailable */ public static void ensureAvailability() { if (UNAVAILABILITY_CAUSE != null) { throw (Error) new UnsatisfiedLinkError( ""failed to load the required native library"").initCause(UNAVAILABILITY_CAUSE); } } /** * Returns the cause of unavailability of * {@code netty-tcnative} and its OpenSSL support. * * @return the cause if unavailable. {@code null} if available. */ public static Throwable unavailabilityCause() { return UNAVAILABILITY_CAUSE; } /** * @deprecated use {@link #availableOpenSslCipherSuites()} */ @Deprecated public static Set availableCipherSuites() { return availableOpenSslCipherSuites(); } /** * Returns all the available OpenSSL cipher suites. * Please note that the returned array may include the cipher suites that are insecure or non-functional. */ public static Set availableOpenSslCipherSuites() { return AVAILABLE_OPENSSL_CIPHER_SUITES; } /** * Returns all the available cipher suites (Java-style). * Please note that the returned array may include the cipher suites that are insecure or non-functional. */ public static Set availableJavaCipherSuites() { return AVAILABLE_JAVA_CIPHER_SUITES; } /** * Returns {@code true} if and only if the specified cipher suite is available in OpenSSL. * Both Java-style cipher suite and OpenSSL-style cipher suite are accepted. */ public static boolean isCipherSuiteAvailable(String cipherSuite) { String converted = CipherSuiteConverter.toOpenSsl(cipherSuite, IS_BORINGSSL); if (converted != null) { cipherSuite = converted; } return AVAILABLE_OPENSSL_CIPHER_SUITES.contains(cipherSuite); } /** * Returns {@code true} if {@link javax.net.ssl.KeyManagerFactory} is supported when using OpenSSL. */ public static boolean supportsKeyManagerFactory() { return SUPPORTS_KEYMANAGER_FACTORY; } /** * Always returns {@code true} if {@link #isAvailable()} returns {@code true}. * * @deprecated Will be removed because hostname validation is always done by a * {@link javax.net.ssl.TrustManager} implementation. */ @Deprecated public static boolean supportsHostnameValidation() { return isAvailable(); } static boolean useKeyManagerFactory() { return USE_KEYMANAGER_FACTORY; } static long memoryAddress(ByteBuf buf) { assert buf.isDirect(); return buf.hasMemoryAddress() ? buf.memoryAddress() : // Use internalNioBuffer to reduce object creation. Buffer.address(buf.internalNioBuffer(0, buf.readableBytes())); } private OpenSsl() { } private static void loadTcNative() throws Exception { String os = PlatformDependent.normalizedOs(); String arch = PlatformDependent.normalizedArch(); Set libNames = new LinkedHashSet(5); String staticLibName = ""netty_tcnative""; // First, try loading the platform-specific library. Platform-specific // libraries will be available if using a tcnative uber jar. if (""linux"".equals(os)) { Set classifiers = PlatformDependent.normalizedLinuxClassifiers(); for (String classifier : classifiers) { libNames.add(staticLibName + ""_"" + os + '_' + arch + ""_"" + classifier); } // generic arch-dependent library libNames.add(staticLibName + ""_"" + os + '_' + arch); // Fedora SSL lib so naming (libssl.so.10 vs libssl.so.1.0.0). // note: should already be included from the classifiers but if not, we use this as an // additional fallback option here libNames.add(staticLibName + ""_"" + os + '_' + arch + ""_fedora""); } else { libNames.add(staticLibName + ""_"" + os + '_' + arch); } libNames.add(staticLibName + ""_"" + arch); libNames.add(staticLibName); NativeLibraryLoader.loadFirstAvailable(PlatformDependent.getClassLoader(SSLContext.class), libNames.toArray(EmptyArrays.EMPTY_STRINGS)); } private static boolean initializeTcNative(String engine) throws Exception { return Library.initialize(""provided"", engine); } static void releaseIfNeeded(ReferenceCounted counted) { if (counted.refCnt() > 0) { ReferenceCountUtil.safeRelease(counted); } } static boolean isTlsv13Supported() { return TLSV13_SUPPORTED; } static boolean isOptionSupported(SslContextOption option) { if (isAvailable()) { if (option == OpenSslContextOption.USE_TASKS) { return true; } // Check for options that are only supported by BoringSSL atm. if (isBoringSSL()) { return option == OpenSslContextOption.ASYNC_PRIVATE_KEY_METHOD || option == OpenSslContextOption.PRIVATE_KEY_METHOD || option == OpenSslContextOption.CERTIFICATE_COMPRESSION_ALGORITHMS || option == OpenSslContextOption.TLS_FALSE_START || option == OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES; } } return false; } private static Set protocols(String property) { String protocolsString = SystemPropertyUtil.get(property, null); if (protocolsString != null) { Set protocols = new HashSet(); for (String proto : protocolsString.split("","")) { String p = proto.trim(); protocols.add(p); } return protocols; } return null; } static String[] defaultProtocols(boolean isClient) { final Collection defaultProtocols = isClient ? CLIENT_DEFAULT_PROTOCOLS : SERVER_DEFAULT_PROTOCOLS; if (defaultProtocols == null) { return null; } List protocols = new ArrayList(defaultProtocols.size()); for (String proto : defaultProtocols) { if (SUPPORTED_PROTOCOLS_SET.contains(proto)) { protocols.add(proto); } } return protocols.toArray(EmptyArrays.EMPTY_STRINGS); } static boolean isBoringSSL() { return IS_BORINGSSL; } } ","privateKey " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.video; import static android.view.Display.DEFAULT_DISPLAY; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import static java.lang.Math.max; import static java.lang.Math.min; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; import android.media.MediaCodec; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.util.Pair; import android.view.Display; import android.view.Surface; import androidx.annotation.CallSuper; import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer2.util.DebugViewProvider; import com.google.android.exoplayer2.util.Effect; import com.google.android.exoplayer2.util.FrameInfo; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MediaFormatUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Size; import com.google.android.exoplayer2.util.SurfaceInfo; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.VideoFrameProcessingException; import com.google.android.exoplayer2.util.VideoFrameProcessor; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Decodes and renders video using {@link MediaCodec}. * *

This renderer accepts the following messages sent via {@link ExoPlayer#createMessage(Target)} * on the playback thread: * *

    *
  • Message with type {@link #MSG_SET_VIDEO_OUTPUT} to set the output. The message payload * should be the target {@link Surface}, or null to clear the output. Other non-null payloads * have the effect of clearing the output. *
  • Message with type {@link #MSG_SET_VIDEO_OUTPUT_RESOLUTION} to set the output resolution. * The message payload should be the output resolution in {@link Size}. *
  • Message with type {@link #MSG_SET_SCALING_MODE} to set the video scaling mode. The message * payload should be one of the integer scaling modes in {@link C.VideoScalingMode}. Note that * the scaling mode only applies if the {@link Surface} targeted by this renderer is owned by * a {@link android.view.SurfaceView}. *
  • Message with type {@link #MSG_SET_CHANGE_FRAME_RATE_STRATEGY} to set the strategy used to * call {@link Surface#setFrameRate}. *
  • Message with type {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER} to set a listener for * metadata associated with frames being rendered. The message payload should be the {@link * VideoFrameMetadataListener}, or null. *
* * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public class MediaCodecVideoRenderer extends MediaCodecRenderer { private static final String TAG = ""MediaCodecVideoRenderer""; private static final String KEY_CROP_LEFT = ""crop-left""; private static final String KEY_CROP_RIGHT = ""crop-right""; private static final String KEY_CROP_BOTTOM = ""crop-bottom""; private static final String KEY_CROP_TOP = ""crop-top""; // Long edge length in pixels for standard video formats, in decreasing in order. private static final int[] STANDARD_LONG_EDGE_VIDEO_PX = new int[] {1920, 1600, 1440, 1280, 960, 854, 640, 540, 480}; /** * Scale factor for the initial maximum input size used to configure the codec in non-adaptive * playbacks. See {@link #getCodecMaxValues(MediaCodecInfo, Format, Format[])}. */ private static final float INITIAL_FORMAT_MAX_INPUT_SIZE_SCALE_FACTOR = 1.5f; /** Magic frame render timestamp that indicates the EOS in tunneling mode. */ private static final long TUNNELING_EOS_PRESENTATION_TIME_US = Long.MAX_VALUE; /** The minimum input buffer size for HEVC. */ private static final int HEVC_MAX_INPUT_SIZE_THRESHOLD = 2 * 1024 * 1024; private static boolean evaluatedDeviceNeedsSetOutputSurfaceWorkaround; private static boolean deviceNeedsSetOutputSurfaceWorkaround; private final Context context; private final VideoFrameReleaseHelper frameReleaseHelper; private final EventDispatcher eventDispatcher; private final VideoFrameProcessorManager videoFrameProcessorManager; private final long allowedJoiningTimeMs; private final int maxDroppedFramesToNotify; private final boolean deviceNeedsNoPostProcessWorkaround; private CodecMaxValues codecMaxValues; private boolean codecNeedsSetOutputSurfaceWorkaround; private boolean codecHandlesHdr10PlusOutOfBandMetadata; @Nullable private Surface displaySurface; @Nullable private PlaceholderSurface placeholderSurface; private boolean haveReportedFirstFrameRenderedForCurrentSurface; private @C.VideoScalingMode int scalingMode; private boolean renderedFirstFrameAfterReset; private boolean mayRenderFirstFrameAfterEnableIfNotStarted; private boolean renderedFirstFrameAfterEnable; private long initialPositionUs; private long joiningDeadlineMs; private long droppedFrameAccumulationStartTimeMs; private int droppedFrames; private int consecutiveDroppedFrameCount; private int buffersInCodecCount; private long lastBufferPresentationTimeUs; private long lastRenderRealtimeUs; private long totalVideoFrameProcessingOffsetUs; private int videoFrameProcessingOffsetCount; private long lastFrameReleaseTimeNs; private VideoSize decodedVideoSize; @Nullable private VideoSize reportedVideoSize; private boolean tunneling; private int tunnelingAudioSessionId; /* package */ @Nullable OnFrameRenderedListenerV23 tunnelingOnFrameRenderedListener; @Nullable private VideoFrameMetadataListener frameMetadataListener; /** * @param context A context. * @param mediaCodecSelector A decoder selector. */ public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector) { this(context, mediaCodecSelector, 0); } /** * @param context A context. * @param mediaCodecSelector A decoder selector. * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * can attempt to seamlessly join an ongoing playback. */ public MediaCodecVideoRenderer( Context context, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs) { this( context, mediaCodecSelector, allowedJoiningTimeMs, /* eventHandler= */ null, /* eventListener= */ null, /* maxDroppedFramesToNotify= */ 0); } /** * @param context A context. * @param mediaCodecSelector A decoder selector. * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * can attempt to seamlessly join an ongoing playback. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. */ public MediaCodecVideoRenderer( Context context, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { this( context, MediaCodecAdapter.Factory.DEFAULT, mediaCodecSelector, allowedJoiningTimeMs, /* enableDecoderFallback= */ false, eventHandler, eventListener, maxDroppedFramesToNotify, /* assumedMinimumCodecOperatingRate= */ 30); } /** * @param context A context. * @param mediaCodecSelector A decoder selector. * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * can attempt to seamlessly join an ongoing playback. * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder * initialization fails. This may result in using a decoder that is slower/less efficient than * the primary decoder. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. */ public MediaCodecVideoRenderer( Context context, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, boolean enableDecoderFallback, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { this( context, MediaCodecAdapter.Factory.DEFAULT, mediaCodecSelector, allowedJoiningTimeMs, enableDecoderFallback, eventHandler, eventListener, maxDroppedFramesToNotify, /* assumedMinimumCodecOperatingRate= */ 30); } /** * @param context A context. * @param codecAdapterFactory The {@link MediaCodecAdapter.Factory} used to create {@link * MediaCodecAdapter} instances. * @param mediaCodecSelector A decoder selector. * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * can attempt to seamlessly join an ongoing playback. * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder * initialization fails. This may result in using a decoder that is slower/less efficient than * the primary decoder. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. */ public MediaCodecVideoRenderer( Context context, MediaCodecAdapter.Factory codecAdapterFactory, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, boolean enableDecoderFallback, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { this( context, codecAdapterFactory, mediaCodecSelector, allowedJoiningTimeMs, enableDecoderFallback, eventHandler, eventListener, maxDroppedFramesToNotify, /* assumedMinimumCodecOperatingRate= */ 30); } /** * Creates a new instance. * * @param context A context. * @param codecAdapterFactory The {@link MediaCodecAdapter.Factory} used to create {@link * MediaCodecAdapter} instances. * @param mediaCodecSelector A decoder selector. * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * can attempt to seamlessly join an ongoing playback. * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder * initialization fails. This may result in using a decoder that is slower/less efficient than * the primary decoder. * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required. * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. * @param assumedMinimumCodecOperatingRate A codec operating rate that all codecs instantiated by * this renderer are assumed to meet implicitly (i.e. without the operating rate being set * explicitly using {@link MediaFormat#KEY_OPERATING_RATE}). */ public MediaCodecVideoRenderer( Context context, MediaCodecAdapter.Factory codecAdapterFactory, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, boolean enableDecoderFallback, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify, float assumedMinimumCodecOperatingRate) { super( C.TRACK_TYPE_VIDEO, codecAdapterFactory, mediaCodecSelector, enableDecoderFallback, assumedMinimumCodecOperatingRate); this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.context = context.getApplicationContext(); frameReleaseHelper = new VideoFrameReleaseHelper(this.context); eventDispatcher = new EventDispatcher(eventHandler, eventListener); videoFrameProcessorManager = new VideoFrameProcessorManager(frameReleaseHelper, /* renderer= */ this); deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround(); joiningDeadlineMs = C.TIME_UNSET; scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; decodedVideoSize = VideoSize.UNKNOWN; tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; clearReportedVideoSize(); } @Override public String getName() { return TAG; } @Override protected @Capabilities int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) throws DecoderQueryException { String mimeType = format.sampleMimeType; if (!MimeTypes.isVideo(mimeType)) { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); } @Nullable DrmInitData drmInitData = format.drmInitData; // Assume encrypted content requires secure decoders. boolean requiresSecureDecryption = drmInitData != null; List decoderInfos = getDecoderInfos( context, mediaCodecSelector, format, requiresSecureDecryption, /* requiresTunnelingDecoder= */ false); if (requiresSecureDecryption && decoderInfos.isEmpty()) { // No secure decoders are available. Fall back to non-secure decoders. decoderInfos = getDecoderInfos( context, mediaCodecSelector, format, /* requiresSecureDecoder= */ false, /* requiresTunnelingDecoder= */ false); } if (decoderInfos.isEmpty()) { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); } if (!supportsFormatDrm(format)) { return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_DRM); } // Check whether the first decoder supports the format. This is the preferred decoder for the // format's MIME type, according to the MediaCodecSelector. MediaCodecInfo decoderInfo = decoderInfos.get(0); boolean isFormatSupported = decoderInfo.isFormatSupported(format); boolean isPreferredDecoder = true; if (!isFormatSupported) { // Check whether any of the other decoders support the format. for (int i = 1; i < decoderInfos.size(); i++) { MediaCodecInfo otherDecoderInfo = decoderInfos.get(i); if (otherDecoderInfo.isFormatSupported(format)) { decoderInfo = otherDecoderInfo; isFormatSupported = true; isPreferredDecoder = false; break; } } } @C.FormatSupport int formatSupport = isFormatSupported ? C.FORMAT_HANDLED : C.FORMAT_EXCEEDS_CAPABILITIES; @AdaptiveSupport int adaptiveSupport = decoderInfo.isSeamlessAdaptationSupported(format) ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS; @HardwareAccelerationSupport int hardwareAccelerationSupport = decoderInfo.hardwareAccelerated ? HARDWARE_ACCELERATION_SUPPORTED : HARDWARE_ACCELERATION_NOT_SUPPORTED; @DecoderSupport int decoderSupport = isPreferredDecoder ? DECODER_SUPPORT_PRIMARY : DECODER_SUPPORT_FALLBACK; if (Util.SDK_INT >= 26 && MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType) && !Api26.doesDisplaySupportDolbyVision(context)) { decoderSupport = DECODER_SUPPORT_FALLBACK_MIMETYPE; } @TunnelingSupport int tunnelingSupport = TUNNELING_NOT_SUPPORTED; if (isFormatSupported) { List tunnelingDecoderInfos = getDecoderInfos( context, mediaCodecSelector, format, requiresSecureDecryption, /* requiresTunnelingDecoder= */ true); if (!tunnelingDecoderInfos.isEmpty()) { MediaCodecInfo tunnelingDecoderInfo = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(tunnelingDecoderInfos, format) .get(0); if (tunnelingDecoderInfo.isFormatSupported(format) && tunnelingDecoderInfo.isSeamlessAdaptationSupported(format)) { tunnelingSupport = TUNNELING_SUPPORTED; } } } return RendererCapabilities.create( formatSupport, adaptiveSupport, tunnelingSupport, hardwareAccelerationSupport, decoderSupport); } @Override protected List getDecoderInfos( MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder) throws DecoderQueryException { return MediaCodecUtil.getDecoderInfosSortedByFormatSupport( getDecoderInfos(context, mediaCodecSelector, format, requiresSecureDecoder, tunneling), format); } /** * Returns a list of decoders that can decode media in the specified format, in the priority order * specified by the {@link MediaCodecSelector}. Note that since the {@link MediaCodecSelector} * only has access to {@link Format#sampleMimeType}, the list is not ordered to account for * whether each decoder supports the details of the format (e.g., taking into account the format's * profile, level, resolution and so on). {@link * MediaCodecUtil#getDecoderInfosSortedByFormatSupport} can be used to further sort the list into * an order where decoders that fully support the format come first. * * @param mediaCodecSelector The decoder selector. * @param format The {@link Format} for which a decoder is required. * @param requiresSecureDecoder Whether a secure decoder is required. * @param requiresTunnelingDecoder Whether a tunneling decoder is required. * @return A list of {@link MediaCodecInfo}s corresponding to decoders. May be empty. * @throws DecoderQueryException Thrown if there was an error querying decoders. */ private static List getDecoderInfos( Context context, MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder, boolean requiresTunnelingDecoder) throws DecoderQueryException { if (format.sampleMimeType == null) { return ImmutableList.of(); } if (Util.SDK_INT >= 26 && MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType) && !Api26.doesDisplaySupportDolbyVision(context)) { List alternativeDecoderInfos = MediaCodecUtil.getAlternativeDecoderInfos( mediaCodecSelector, format, requiresSecureDecoder, requiresTunnelingDecoder); if (!alternativeDecoderInfos.isEmpty()) { return alternativeDecoderInfos; } } return MediaCodecUtil.getDecoderInfosSoftMatch( mediaCodecSelector, format, requiresSecureDecoder, requiresTunnelingDecoder); } @RequiresApi(26) private static final class Api26 { @DoNotInline public static boolean doesDisplaySupportDolbyVision(Context context) { boolean supportsDolbyVision = false; DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); Display display = (displayManager != null) ? displayManager.getDisplay(DEFAULT_DISPLAY) : null; if (display != null && display.isHdr()) { int[] supportedHdrTypes = display.getHdrCapabilities().getSupportedHdrTypes(); for (int hdrType : supportedHdrTypes) { if (hdrType == Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION) { supportsDolbyVision = true; break; } } } return supportsDolbyVision; } } @Override protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) throws ExoPlaybackException { super.onEnabled(joining, mayRenderStartOfStream); boolean tunneling = getConfiguration().tunneling; checkState(!tunneling || tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET); if (this.tunneling != tunneling) { this.tunneling = tunneling; releaseCodec(); } eventDispatcher.enabled(decoderCounters); mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream; renderedFirstFrameAfterEnable = false; } @Override protected void onPositionReset(long [MASK] , boolean joining) throws ExoPlaybackException { super.onPositionReset( [MASK] , joining); if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.flush(); } clearRenderedFirstFrame(); frameReleaseHelper.onPositionReset(); lastBufferPresentationTimeUs = C.TIME_UNSET; initialPositionUs = C.TIME_UNSET; consecutiveDroppedFrameCount = 0; if (joining) { setJoiningDeadlineMs(); } else { joiningDeadlineMs = C.TIME_UNSET; } } @Override public boolean isEnded() { boolean isEnded = super.isEnded(); if (videoFrameProcessorManager.isEnabled()) { isEnded &= videoFrameProcessorManager.releasedLastFrame(); } return isEnded; } @Override public boolean isReady() { if (super.isReady() && (!videoFrameProcessorManager.isEnabled() || videoFrameProcessorManager.isReady()) && (renderedFirstFrameAfterReset || (placeholderSurface != null && displaySurface == placeholderSurface) || getCodec() == null || tunneling)) { // Ready. If we were joining then we've now joined, so clear the joining deadline. joiningDeadlineMs = C.TIME_UNSET; return true; } else if (joiningDeadlineMs == C.TIME_UNSET) { // Not joining. return false; } else if (SystemClock.elapsedRealtime() < joiningDeadlineMs) { // Joining and still within the joining deadline. return true; } else { // The joining deadline has been exceeded. Give up and clear the deadline. joiningDeadlineMs = C.TIME_UNSET; return false; } } @Override protected void onStarted() { super.onStarted(); droppedFrames = 0; droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); lastRenderRealtimeUs = SystemClock.elapsedRealtime() * 1000; totalVideoFrameProcessingOffsetUs = 0; videoFrameProcessingOffsetCount = 0; frameReleaseHelper.onStarted(); } @Override protected void onStopped() { joiningDeadlineMs = C.TIME_UNSET; maybeNotifyDroppedFrames(); maybeNotifyVideoFrameProcessingOffset(); frameReleaseHelper.onStopped(); super.onStopped(); } @Override protected void onDisabled() { clearReportedVideoSize(); clearRenderedFirstFrame(); haveReportedFirstFrameRenderedForCurrentSurface = false; tunnelingOnFrameRenderedListener = null; try { super.onDisabled(); } finally { eventDispatcher.disabled(decoderCounters); eventDispatcher.videoSizeChanged(VideoSize.UNKNOWN); } } @TargetApi(17) // Needed for placeholderSurface usage, as it is always null on API level 16. @Override protected void onReset() { try { super.onReset(); } finally { if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.reset(); } if (placeholderSurface != null) { releasePlaceholderSurface(); } } } @Override public void handleMessage(@MessageType int messageType, @Nullable Object message) throws ExoPlaybackException { switch (messageType) { case MSG_SET_VIDEO_OUTPUT: setOutput(message); break; case MSG_SET_SCALING_MODE: scalingMode = (Integer) message; @Nullable MediaCodecAdapter codec = getCodec(); if (codec != null) { codec.setVideoScalingMode(scalingMode); } break; case MSG_SET_CHANGE_FRAME_RATE_STRATEGY: frameReleaseHelper.setChangeFrameRateStrategy((int) message); break; case MSG_SET_VIDEO_FRAME_METADATA_LISTENER: frameMetadataListener = (VideoFrameMetadataListener) message; break; case MSG_SET_AUDIO_SESSION_ID: int tunnelingAudioSessionId = (int) message; if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { this.tunnelingAudioSessionId = tunnelingAudioSessionId; if (tunneling) { releaseCodec(); } } break; case MSG_SET_VIDEO_EFFECTS: @SuppressWarnings(""unchecked"") List videoEffects = (List) checkNotNull(message); videoFrameProcessorManager.setVideoEffects(videoEffects); break; case MSG_SET_VIDEO_OUTPUT_RESOLUTION: Size outputResolution = (Size) checkNotNull(message); if (outputResolution.getWidth() != 0 && outputResolution.getHeight() != 0 && displaySurface != null) { videoFrameProcessorManager.setOutputSurfaceInfo(displaySurface, outputResolution); } break; case MSG_SET_AUDIO_ATTRIBUTES: case MSG_SET_AUX_EFFECT_INFO: case MSG_SET_CAMERA_MOTION_LISTENER: case MSG_SET_SKIP_SILENCE_ENABLED: case MSG_SET_VOLUME: case MSG_SET_WAKEUP_LISTENER: default: super.handleMessage(messageType, message); } } private void setOutput(@Nullable Object output) throws ExoPlaybackException { // Handle unsupported (i.e., non-Surface) outputs by clearing the display surface. @Nullable Surface displaySurface = output instanceof Surface ? (Surface) output : null; if (displaySurface == null) { // Use a placeholder surface if possible. if (placeholderSurface != null) { displaySurface = placeholderSurface; } else { MediaCodecInfo codecInfo = getCodecInfo(); if (codecInfo != null && shouldUsePlaceholderSurface(codecInfo)) { placeholderSurface = PlaceholderSurface.newInstanceV17(context, codecInfo.secure); displaySurface = placeholderSurface; } } } // We only need to update the codec if the display surface has changed. if (this.displaySurface != displaySurface) { this.displaySurface = displaySurface; frameReleaseHelper.onSurfaceChanged(displaySurface); haveReportedFirstFrameRenderedForCurrentSurface = false; @State int state = getState(); @Nullable MediaCodecAdapter codec = getCodec(); if (codec != null && !videoFrameProcessorManager.isEnabled()) { if (Util.SDK_INT >= 23 && displaySurface != null && !codecNeedsSetOutputSurfaceWorkaround) { setOutputSurfaceV23(codec, displaySurface); } else { releaseCodec(); maybeInitCodecOrBypass(); } } if (displaySurface != null && displaySurface != placeholderSurface) { // If we know the video size, report it again immediately. maybeRenotifyVideoSizeChanged(); // We haven't rendered to the new display surface yet. clearRenderedFirstFrame(); if (state == STATE_STARTED) { // Set joining deadline to report MediaCodecVideoRenderer is ready. setJoiningDeadlineMs(); } // When VideoFrameProcessorManager is enabled, set VideoFrameProcessorManager's display // surface and an unknown size. if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.setOutputSurfaceInfo(displaySurface, Size.UNKNOWN); } } else { // The display surface has been removed. clearReportedVideoSize(); clearRenderedFirstFrame(); if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.clearOutputSurfaceInfo(); } } } else if (displaySurface != null && displaySurface != placeholderSurface) { // The display surface is set and unchanged. If we know the video size and/or have already // rendered to the display surface, report these again immediately. maybeRenotifyVideoSizeChanged(); maybeRenotifyRenderedFirstFrame(); } } @Override protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { return displaySurface != null || shouldUsePlaceholderSurface(codecInfo); } @Override protected boolean getCodecNeedsEosPropagation() { // Since API 23, onFrameRenderedListener allows for detection of the renderer EOS. return tunneling && Util.SDK_INT < 23; } @TargetApi(17) // Needed for placeHolderSurface usage, as it is always null on API level 16. @Override protected MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, Format format, @Nullable MediaCrypto crypto, float codecOperatingRate) { if (placeholderSurface != null && placeholderSurface.secure != codecInfo.secure) { // We can't re-use the current DummySurface instance with the new decoder. releasePlaceholderSurface(); } String codecMimeType = codecInfo.codecMimeType; codecMaxValues = getCodecMaxValues(codecInfo, format, getStreamFormats()); MediaFormat mediaFormat = getMediaFormat( format, codecMimeType, codecMaxValues, codecOperatingRate, deviceNeedsNoPostProcessWorkaround, tunneling ? tunnelingAudioSessionId : C.AUDIO_SESSION_ID_UNSET); if (displaySurface == null) { if (!shouldUsePlaceholderSurface(codecInfo)) { throw new IllegalStateException(); } if (placeholderSurface == null) { placeholderSurface = PlaceholderSurface.newInstanceV17(context, codecInfo.secure); } displaySurface = placeholderSurface; } if (videoFrameProcessorManager.isEnabled()) { mediaFormat = videoFrameProcessorManager.amendMediaFormatKeys(mediaFormat); } return MediaCodecAdapter.Configuration.createForVideoDecoding( codecInfo, mediaFormat, format, videoFrameProcessorManager.isEnabled() ? videoFrameProcessorManager.getInputSurface() : displaySurface, crypto); } @Override protected DecoderReuseEvaluation canReuseCodec( MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat); @DecoderDiscardReasons int discardReasons = evaluation.discardReasons; if (newFormat.width > codecMaxValues.width || newFormat.height > codecMaxValues.height) { discardReasons |= DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED; } if (getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) { discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED; } return new DecoderReuseEvaluation( codecInfo.name, oldFormat, newFormat, discardReasons != 0 ? REUSE_RESULT_NO : evaluation.result, discardReasons); } @CallSuper @Override public void render(long [MASK] , long elapsedRealtimeUs) throws ExoPlaybackException { super.render( [MASK] , elapsedRealtimeUs); if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.releaseProcessedFrames( [MASK] , elapsedRealtimeUs); } } @CallSuper @Override protected void resetCodecStateForFlush() { super.resetCodecStateForFlush(); buffersInCodecCount = 0; } @Override public void setPlaybackSpeed(float currentPlaybackSpeed, float targetPlaybackSpeed) throws ExoPlaybackException { super.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed); frameReleaseHelper.onPlaybackSpeed(currentPlaybackSpeed); } /** * Returns a maximum input size for a given codec and format. * * @param codecInfo Information about the {@link MediaCodec} being configured. * @param format The format. * @return A maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be * determined. */ public static int getCodecMaxInputSize(MediaCodecInfo codecInfo, Format format) { int width = format.width; int height = format.height; if (width == Format.NO_VALUE || height == Format.NO_VALUE) { // We can't infer a maximum input size without video dimensions. return Format.NO_VALUE; } String sampleMimeType = format.sampleMimeType; if (MimeTypes.VIDEO_DOLBY_VISION.equals(sampleMimeType)) { // Dolby vision can be a wrapper around H264 or H265. We assume it's wrapping H265 by default // because it's the common case, and because some devices may fail to allocate the codec when // the larger buffer size required for H264 is requested. We size buffers for H264 only if the // format contains sufficient information for us to determine unambiguously that it's a H264 // profile. sampleMimeType = MimeTypes.VIDEO_H265; @Nullable Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); if (codecProfileAndLevel != null) { int profile = codecProfileAndLevel.first; if (profile == CodecProfileLevel.DolbyVisionProfileDvavSe || profile == CodecProfileLevel.DolbyVisionProfileDvavPer || profile == CodecProfileLevel.DolbyVisionProfileDvavPen) { sampleMimeType = MimeTypes.VIDEO_H264; } } } // Attempt to infer a maximum input size from the format. switch (sampleMimeType) { case MimeTypes.VIDEO_H263: case MimeTypes.VIDEO_MP4V: case MimeTypes.VIDEO_AV1: // Assume a min compression of 2 similar to the platform's C2SoftAomDec.cpp. case MimeTypes.VIDEO_VP8: // Assume a min compression of 2 similar to the platform's SoftVPX.cpp. return getMaxSampleSize(/* pixelCount= */ width * height, /* minCompressionRatio= */ 2); case MimeTypes.VIDEO_H265: // Assume a min compression of 2 similar to the platform's C2SoftHevcDec.cpp, but restrict // the minimum size. return max( HEVC_MAX_INPUT_SIZE_THRESHOLD, getMaxSampleSize(/* pixelCount= */ width * height, /* minCompressionRatio= */ 2)); case MimeTypes.VIDEO_H264: if (""BRAVIA 4K 2015"".equals(Util.MODEL) // Sony Bravia 4K || (""Amazon"".equals(Util.MANUFACTURER) && (""KFSOWI"".equals(Util.MODEL) // Kindle Soho || (""AFTS"".equals(Util.MODEL) && codecInfo.secure)))) { // Fire TV Gen 2 // Use the default value for cases where platform limitations may prevent buffers of the // calculated maximum input size from being allocated. return Format.NO_VALUE; } // Round up width/height to an integer number of macroblocks. int maxPixels = Util.ceilDivide(width, 16) * Util.ceilDivide(height, 16) * 16 * 16; return getMaxSampleSize(maxPixels, /* minCompressionRatio= */ 2); case MimeTypes.VIDEO_VP9: return getMaxSampleSize(/* pixelCount= */ width * height, /* minCompressionRatio= */ 4); default: // Leave the default max input size. return Format.NO_VALUE; } } @Override protected float getCodecOperatingRateV23( float targetPlaybackSpeed, Format format, Format[] streamFormats) { // Use the highest known stream frame-rate up front, to avoid having to reconfigure the codec // should an adaptive switch to that stream occur. float maxFrameRate = -1; for (Format streamFormat : streamFormats) { float streamFrameRate = streamFormat.frameRate; if (streamFrameRate != Format.NO_VALUE) { maxFrameRate = max(maxFrameRate, streamFrameRate); } } return maxFrameRate == -1 ? CODEC_OPERATING_RATE_UNSET : (maxFrameRate * targetPlaybackSpeed); } @CallSuper @Override protected void onReadyToInitializeCodec(Format format) throws ExoPlaybackException { if (!videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.maybeEnable(format, getOutputStreamOffsetUs()); } } @Override protected void onCodecInitialized( String name, MediaCodecAdapter.Configuration configuration, long initializedTimestampMs, long initializationDurationMs) { eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name); codecHandlesHdr10PlusOutOfBandMetadata = checkNotNull(getCodecInfo()).isHdr10PlusOutOfBandMetadataSupported(); if (Util.SDK_INT >= 23 && tunneling) { tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(checkNotNull(getCodec())); } videoFrameProcessorManager.onCodecInitialized(name); } @Override protected void onCodecReleased(String name) { eventDispatcher.decoderReleased(name); } @Override protected void onCodecError(Exception codecError) { Log.e(TAG, ""Video codec error"", codecError); eventDispatcher.videoCodecError(codecError); } @Override @Nullable protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException { @Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder); eventDispatcher.inputFormatChanged(formatHolder.format, evaluation); return evaluation; } /** * Called immediately before an input buffer is queued into the codec. * *

In tunneling mode for pre Marshmallow, the buffer is treated as if immediately output. * * @param buffer The buffer to be queued. * @throws ExoPlaybackException Thrown if an error occurs handling the input buffer. */ @CallSuper @Override protected void onQueueInputBuffer(DecoderInputBuffer buffer) throws ExoPlaybackException { // In tunneling mode the device may do frame rate conversion, so in general we can't keep track // of the number of buffers in the codec. if (!tunneling) { buffersInCodecCount++; } if (Util.SDK_INT < 23 && tunneling) { // In tunneled mode before API 23 we don't have a way to know when the buffer is output, so // treat it as if it were output immediately. onProcessedTunneledBuffer(buffer.timeUs); } } @Override protected void onOutputFormatChanged(Format format, @Nullable MediaFormat mediaFormat) { @Nullable MediaCodecAdapter codec = getCodec(); if (codec != null) { // Must be applied each time the output format changes. codec.setVideoScalingMode(scalingMode); } int width; int height; int unappliedRotationDegrees = 0; float pixelWidthHeightRatio; if (tunneling) { width = format.width; height = format.height; } else { checkNotNull(mediaFormat); boolean hasCrop = mediaFormat.containsKey(KEY_CROP_RIGHT) && mediaFormat.containsKey(KEY_CROP_LEFT) && mediaFormat.containsKey(KEY_CROP_BOTTOM) && mediaFormat.containsKey(KEY_CROP_TOP); width = hasCrop ? mediaFormat.getInteger(KEY_CROP_RIGHT) - mediaFormat.getInteger(KEY_CROP_LEFT) + 1 : mediaFormat.getInteger(MediaFormat.KEY_WIDTH); height = hasCrop ? mediaFormat.getInteger(KEY_CROP_BOTTOM) - mediaFormat.getInteger(KEY_CROP_TOP) + 1 : mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); } pixelWidthHeightRatio = format.pixelWidthHeightRatio; if (codecAppliesRotation()) { // On API level 21 and above the decoder applies the rotation when rendering to the surface. // Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need // to flip the width, height and pixel aspect ratio to reflect the rotation that was applied. if (format.rotationDegrees == 90 || format.rotationDegrees == 270) { int rotatedHeight = width; width = height; height = rotatedHeight; pixelWidthHeightRatio = 1 / pixelWidthHeightRatio; } } else if (!videoFrameProcessorManager.isEnabled()) { // Neither the codec nor the VideoFrameProcessor applies the rotation. unappliedRotationDegrees = format.rotationDegrees; } decodedVideoSize = new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); frameReleaseHelper.onFormatChanged(format.frameRate); if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.setInputFormat( format .buildUpon() .setWidth(width) .setHeight(height) .setRotationDegrees(unappliedRotationDegrees) .setPixelWidthHeightRatio(pixelWidthHeightRatio) .build()); } } @Override @TargetApi(29) // codecHandlesHdr10PlusOutOfBandMetadata is false if Util.SDK_INT < 29 protected void handleInputBufferSupplementalData(DecoderInputBuffer buffer) throws ExoPlaybackException { if (!codecHandlesHdr10PlusOutOfBandMetadata) { return; } ByteBuffer data = checkNotNull(buffer.supplementalData); if (data.remaining() >= 7) { // Check for HDR10+ out-of-band metadata. See User_data_registered_itu_t_t35 in ST 2094-40. byte ituTT35CountryCode = data.get(); int ituTT35TerminalProviderCode = data.getShort(); int ituTT35TerminalProviderOrientedCode = data.getShort(); byte applicationIdentifier = data.get(); byte applicationVersion = data.get(); data.position(0); if (ituTT35CountryCode == (byte) 0xB5 && ituTT35TerminalProviderCode == 0x003C && ituTT35TerminalProviderOrientedCode == 0x0001 && applicationIdentifier == 4 && (applicationVersion == 0 || applicationVersion == 1)) { // The metadata size may vary so allocate a new array every time. This is not too // inefficient because the metadata is only a few tens of bytes. byte[] hdr10PlusInfo = new byte[data.remaining()]; data.get(hdr10PlusInfo); data.position(0); setHdr10PlusInfoV29(getCodec(), hdr10PlusInfo); } } } @Override protected boolean processOutputBuffer( long [MASK] , long elapsedRealtimeUs, @Nullable MediaCodecAdapter codec, @Nullable ByteBuffer buffer, int bufferIndex, int bufferFlags, int sampleCount, long bufferPresentationTimeUs, boolean isDecodeOnlyBuffer, boolean isLastBuffer, Format format) throws ExoPlaybackException { checkNotNull(codec); // Can not render video without codec if (initialPositionUs == C.TIME_UNSET) { initialPositionUs = [MASK] ; } if (bufferPresentationTimeUs != lastBufferPresentationTimeUs) { if (!videoFrameProcessorManager.isEnabled()) { frameReleaseHelper.onNextFrame(bufferPresentationTimeUs); } // else, update the frameReleaseHelper when releasing the processed frames. this.lastBufferPresentationTimeUs = bufferPresentationTimeUs; } long outputStreamOffsetUs = getOutputStreamOffsetUs(); long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs; if (isDecodeOnlyBuffer && !isLastBuffer) { skipOutputBuffer(codec, bufferIndex, presentationTimeUs); return true; } // Note: Use of double rather than float is intentional for accuracy in the calculations below. boolean isStarted = getState() == STATE_STARTED; long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000; long earlyUs = calculateEarlyTimeUs( [MASK] , elapsedRealtimeUs, elapsedRealtimeNowUs, bufferPresentationTimeUs, isStarted); if (displaySurface == placeholderSurface) { // Skip frames in sync with playback, so we'll be at the right frame if the mode changes. if (isBufferLate(earlyUs)) { skipOutputBuffer(codec, bufferIndex, presentationTimeUs); updateVideoFrameProcessingOffsetCounters(earlyUs); return true; } return false; } boolean forceRenderOutputBuffer = shouldForceRender( [MASK] , earlyUs); if (forceRenderOutputBuffer) { boolean notifyFrameMetaDataListener; if (videoFrameProcessorManager.isEnabled()) { notifyFrameMetaDataListener = false; if (!videoFrameProcessorManager.maybeRegisterFrame( format, presentationTimeUs, isLastBuffer)) { return false; } } else { notifyFrameMetaDataListener = true; } renderOutputBufferNow( codec, format, bufferIndex, presentationTimeUs, notifyFrameMetaDataListener); updateVideoFrameProcessingOffsetCounters(earlyUs); return true; } if (!isStarted || [MASK] == initialPositionUs) { return false; } // Compute the buffer's desired release time in nanoseconds. long systemTimeNs = System.nanoTime(); long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000); // Apply a timestamp adjustment, if there is one. long adjustedReleaseTimeNs = frameReleaseHelper.adjustReleaseTime(unadjustedFrameReleaseTimeNs); if (!videoFrameProcessorManager.isEnabled()) { earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000; } // else, use the unadjusted earlyUs in previewing use cases. boolean treatDroppedBuffersAsSkipped = joiningDeadlineMs != C.TIME_UNSET; if (shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs, isLastBuffer) && maybeDropBuffersToKeyframe( [MASK] , treatDroppedBuffersAsSkipped)) { return false; } else if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs, isLastBuffer)) { if (treatDroppedBuffersAsSkipped) { skipOutputBuffer(codec, bufferIndex, presentationTimeUs); } else { dropOutputBuffer(codec, bufferIndex, presentationTimeUs); } updateVideoFrameProcessingOffsetCounters(earlyUs); return true; } if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.releaseProcessedFrames( [MASK] , elapsedRealtimeUs); if (videoFrameProcessorManager.maybeRegisterFrame(format, presentationTimeUs, isLastBuffer)) { renderOutputBufferNow( codec, format, bufferIndex, presentationTimeUs, /* notifyFrameMetadataListener= */ false); return true; } return false; } if (Util.SDK_INT >= 21) { // Let the underlying framework time the release. if (earlyUs < 50000) { if (adjustedReleaseTimeNs == lastFrameReleaseTimeNs) { // This frame should be displayed on the same vsync with the previous released frame. We // are likely rendering frames at a rate higher than the screen refresh rate. Skip // this buffer so that it's returned to MediaCodec sooner otherwise MediaCodec may not // be able to keep decoding with this rate [b/263454203]. skipOutputBuffer(codec, bufferIndex, presentationTimeUs); } else { notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format); renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, adjustedReleaseTimeNs); } updateVideoFrameProcessingOffsetCounters(earlyUs); lastFrameReleaseTimeNs = adjustedReleaseTimeNs; return true; } } else { // We need to time the release ourselves. if (earlyUs < 30000) { if (earlyUs > 11000) { // We're a little too early to render the frame. Sleep until the frame can be rendered. // Note: The 11ms threshold was chosen fairly arbitrarily. try { // Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms. Thread.sleep((earlyUs - 10000) / 1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } notifyFrameMetadataListener(presentationTimeUs, adjustedReleaseTimeNs, format); renderOutputBuffer(codec, bufferIndex, presentationTimeUs); updateVideoFrameProcessingOffsetCounters(earlyUs); return true; } } // We're either not playing, or it's not time to render the frame yet. return false; } /** Returns whether a buffer or a processed frame should be force rendered. */ private boolean shouldForceRender(long [MASK] , long earlyUs) { boolean isStarted = getState() == STATE_STARTED; boolean shouldRenderFirstFrame = !renderedFirstFrameAfterEnable ? (isStarted || mayRenderFirstFrameAfterEnableIfNotStarted) : !renderedFirstFrameAfterReset; long elapsedSinceLastRenderUs = SystemClock.elapsedRealtime() * 1000 - lastRenderRealtimeUs; // Don't force output until we joined and the position reached the current stream. return joiningDeadlineMs == C.TIME_UNSET && [MASK] >= getOutputStreamOffsetUs() && (shouldRenderFirstFrame || (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs))); } /** * Calculates the time interval between the current player position and the buffer presentation * time. * * @param [MASK] The current media time in microseconds, measured at the start of the current * iteration of the rendering loop. * @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the * start of the current iteration of the rendering loop. * @param elapsedRealtimeNowUs {@link SystemClock#elapsedRealtime()} in microseconds, measured * before calling this method. * @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds, * with {@linkplain #getOutputStreamOffsetUs() stream offset added}. * @param isStarted Whether the playback is in {@link #STATE_STARTED}. * @return The calculated early time, in microseconds. */ private long calculateEarlyTimeUs( long [MASK] , long elapsedRealtimeUs, long elapsedRealtimeNowUs, long bufferPresentationTimeUs, boolean isStarted) { // Note: Use of double rather than float is intentional for accuracy in the calculations below. double playbackSpeed = getPlaybackSpeed(); // Calculate how early we are. In other words, the realtime duration that needs to elapse whilst // the renderer is started before the frame should be rendered. A negative value means that // we're already late. long earlyUs = (long) ((bufferPresentationTimeUs - [MASK] ) / playbackSpeed); if (isStarted) { // Account for the elapsed time since the start of this iteration of the rendering loop. earlyUs -= elapsedRealtimeNowUs - elapsedRealtimeUs; } return earlyUs; } private void notifyFrameMetadataListener( long presentationTimeUs, long releaseTimeNs, Format format) { if (frameMetadataListener != null) { frameMetadataListener.onVideoFrameAboutToBeRendered( presentationTimeUs, releaseTimeNs, format, getCodecOutputMediaFormat()); } } /** Called when a buffer was processed in tunneling mode. */ protected void onProcessedTunneledBuffer(long presentationTimeUs) throws ExoPlaybackException { updateOutputFormatForTime(presentationTimeUs); maybeNotifyVideoSizeChanged(decodedVideoSize); decoderCounters.renderedOutputBufferCount++; maybeNotifyRenderedFirstFrame(); onProcessedOutputBuffer(presentationTimeUs); } /** Called when a output EOS was received in tunneling mode. */ private void onProcessedTunneledEndOfStream() { setPendingOutputEndOfStream(); } @CallSuper @Override protected void onProcessedOutputBuffer(long presentationTimeUs) { super.onProcessedOutputBuffer(presentationTimeUs); if (!tunneling) { buffersInCodecCount--; } } @Override protected void onProcessedStreamChange() { super.onProcessedStreamChange(); clearRenderedFirstFrame(); } /** * Returns whether the buffer being processed should be dropped. * * @param earlyUs The time until the buffer should be presented in microseconds. A negative value * indicates that the buffer is late. * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, * measured at the start of the current iteration of the rendering loop. * @param isLastBuffer Whether the buffer is the last buffer in the current stream. */ protected boolean shouldDropOutputBuffer( long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) { return isBufferLate(earlyUs) && !isLastBuffer; } /** * Returns whether to drop all buffers from the buffer being processed to the keyframe at or after * the current playback position, if possible. * * @param earlyUs The time until the current buffer should be presented in microseconds. A * negative value indicates that the buffer is late. * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, * measured at the start of the current iteration of the rendering loop. * @param isLastBuffer Whether the buffer is the last buffer in the current stream. */ protected boolean shouldDropBuffersToKeyframe( long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) { return isBufferVeryLate(earlyUs) && !isLastBuffer; } /** * Returns whether to force rendering an output buffer. * * @param earlyUs The time until the current buffer should be presented in microseconds. A * negative value indicates that the buffer is late. * @param elapsedSinceLastRenderUs The elapsed time since the last output buffer was rendered, in * microseconds. * @return Returns whether to force rendering an output buffer. */ protected boolean shouldForceRenderOutputBuffer(long earlyUs, long elapsedSinceLastRenderUs) { // Force render late buffers every 100ms to avoid frozen video effect. return isBufferLate(earlyUs) && elapsedSinceLastRenderUs > 100000; } /** * Skips the output buffer with the specified index. * * @param codec The codec that owns the output buffer. * @param index The index of the output buffer to skip. * @param presentationTimeUs The presentation time of the output buffer, in microseconds. */ protected void skipOutputBuffer(MediaCodecAdapter codec, int index, long presentationTimeUs) { TraceUtil.beginSection(""skipVideoBuffer""); codec.releaseOutputBuffer(index, false); TraceUtil.endSection(); decoderCounters.skippedOutputBufferCount++; } /** * Drops the output buffer with the specified index. * * @param codec The codec that owns the output buffer. * @param index The index of the output buffer to drop. * @param presentationTimeUs The presentation time of the output buffer, in microseconds. */ protected void dropOutputBuffer(MediaCodecAdapter codec, int index, long presentationTimeUs) { TraceUtil.beginSection(""dropVideoBuffer""); codec.releaseOutputBuffer(index, false); TraceUtil.endSection(); updateDroppedBufferCounters( /* droppedInputBufferCount= */ 0, /* droppedDecoderBufferCount= */ 1); } /** * Drops frames from the current output buffer to the next keyframe at or before the playback * position. If no such keyframe exists, as the playback position is inside the same group of * pictures as the buffer being processed, returns {@code false}. Returns {@code true} otherwise. * * @param [MASK] The current playback position, in microseconds. * @param treatDroppedBuffersAsSkipped Whether dropped buffers should be treated as intentionally * skipped. * @return Whether any buffers were dropped. * @throws ExoPlaybackException If an error occurs flushing the codec. */ protected boolean maybeDropBuffersToKeyframe( long [MASK] , boolean treatDroppedBuffersAsSkipped) throws ExoPlaybackException { int droppedSourceBufferCount = skipSource( [MASK] ); if (droppedSourceBufferCount == 0) { return false; } // We dropped some buffers to catch up, so update the decoder counters and flush the codec, // which releases all pending buffers buffers including the current output buffer. if (treatDroppedBuffersAsSkipped) { decoderCounters.skippedInputBufferCount += droppedSourceBufferCount; decoderCounters.skippedOutputBufferCount += buffersInCodecCount; } else { decoderCounters.droppedToKeyframeCount++; updateDroppedBufferCounters( droppedSourceBufferCount, /* droppedDecoderBufferCount= */ buffersInCodecCount); } flushOrReinitializeCodec(); if (videoFrameProcessorManager.isEnabled()) { videoFrameProcessorManager.flush(); } return true; } /** * Updates local counters and {@link #decoderCounters} to reflect that buffers were dropped. * * @param droppedInputBufferCount The number of buffers dropped from the source before being * passed to the decoder. * @param droppedDecoderBufferCount The number of buffers dropped after being passed to the * decoder. */ protected void updateDroppedBufferCounters( int droppedInputBufferCount, int droppedDecoderBufferCount) { decoderCounters.droppedInputBufferCount += droppedInputBufferCount; int totalDroppedBufferCount = droppedInputBufferCount + droppedDecoderBufferCount; decoderCounters.droppedBufferCount += totalDroppedBufferCount; droppedFrames += totalDroppedBufferCount; consecutiveDroppedFrameCount += totalDroppedBufferCount; decoderCounters.maxConsecutiveDroppedBufferCount = max(consecutiveDroppedFrameCount, decoderCounters.maxConsecutiveDroppedBufferCount); if (maxDroppedFramesToNotify > 0 && droppedFrames >= maxDroppedFramesToNotify) { maybeNotifyDroppedFrames(); } } /** * Updates local counters and {@link DecoderCounters} with a new video frame processing offset. * * @param processingOffsetUs The video frame processing offset. */ protected void updateVideoFrameProcessingOffsetCounters(long processingOffsetUs) { decoderCounters.addVideoFrameProcessingOffset(processingOffsetUs); totalVideoFrameProcessingOffsetUs += processingOffsetUs; videoFrameProcessingOffsetCount++; } /** * Returns a {@link Pair} of {@linkplain ColorInfo input color} and {@linkplain ColorInfo output * color} to configure the {@code VideoFrameProcessor}. */ protected Pair experimentalGetVideoFrameProcessorColorConfiguration( @Nullable ColorInfo inputColorInfo) { // TODO(b/279163661) Remove this method after VideoFrameProcessor supports texture ID // input/output. if (!ColorInfo.isTransferHdr(inputColorInfo)) { return Pair.create(ColorInfo.SDR_BT709_LIMITED, ColorInfo.SDR_BT709_LIMITED); } if (inputColorInfo.colorTransfer == C.COLOR_TRANSFER_HLG) { // SurfaceView only supports BT2020 PQ input, converting HLG to PQ. return Pair.create( inputColorInfo, inputColorInfo.buildUpon().setColorTransfer(C.COLOR_TRANSFER_ST2084).build()); } return Pair.create(inputColorInfo, inputColorInfo); } /** * Renders the output buffer with the specified index now. * * @param codec The codec that owns the output buffer. * @param format The {@link Format} associated with the buffer. * @param index The index of the output buffer to drop. * @param presentationTimeUs The presentation time of the output buffer, in microseconds. * @param notifyFrameMetadataListener Whether to notify the {@link VideoFrameMetadataListener}. */ private void renderOutputBufferNow( MediaCodecAdapter codec, Format format, int index, long presentationTimeUs, boolean notifyFrameMetadataListener) { // In previewing mode, use the presentation time as release time so that the SurfaceTexture is // accompanied by the rendered frame's presentation time. Setting a realtime based release time // is only relevant when rendering to a SurfaceView (that is when not using VideoFrameProcessor) // for better frame release. In previewing mode MediaCodec renders to VideoFrameProcessor's // input surface, which is not a SurfaceView. long releaseTimeNs = videoFrameProcessorManager.isEnabled() ? videoFrameProcessorManager.getCorrectedFramePresentationTimeUs( presentationTimeUs, getOutputStreamOffsetUs()) * 1000 : System.nanoTime(); if (notifyFrameMetadataListener) { notifyFrameMetadataListener(presentationTimeUs, releaseTimeNs, format); } if (Util.SDK_INT >= 21) { renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs); } else { renderOutputBuffer(codec, index, presentationTimeUs); } } /** * Renders the output buffer with the specified index. This method is only called if the platform * API version of the device is less than 21. * *

When video frame processing is {@linkplain VideoFrameProcessorManager#isEnabled()} enabled}, * this method renders to {@link VideoFrameProcessorManager}'s {@linkplain * VideoFrameProcessorManager#getInputSurface() input surface}. * * @param codec The codec that owns the output buffer. * @param index The index of the output buffer to drop. * @param presentationTimeUs The presentation time of the output buffer, in microseconds. */ protected void renderOutputBuffer(MediaCodecAdapter codec, int index, long presentationTimeUs) { TraceUtil.beginSection(""releaseOutputBuffer""); codec.releaseOutputBuffer(index, true); TraceUtil.endSection(); decoderCounters.renderedOutputBufferCount++; consecutiveDroppedFrameCount = 0; if (!videoFrameProcessorManager.isEnabled()) { lastRenderRealtimeUs = SystemClock.elapsedRealtime() * 1000; maybeNotifyVideoSizeChanged(decodedVideoSize); maybeNotifyRenderedFirstFrame(); } } /** * Renders the output buffer with the specified index. This method is only called if the platform * API version of the device is 21 or later. * *

When video frame processing is {@linkplain VideoFrameProcessorManager#isEnabled()} enabled}, * this method renders to {@link VideoFrameProcessorManager}'s {@linkplain * VideoFrameProcessorManager#getInputSurface() input surface}. * * @param codec The codec that owns the output buffer. * @param index The index of the output buffer to drop. * @param presentationTimeUs The presentation time of the output buffer, in microseconds. * @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds. */ @RequiresApi(21) protected void renderOutputBufferV21( MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) { TraceUtil.beginSection(""releaseOutputBuffer""); codec.releaseOutputBuffer(index, releaseTimeNs); TraceUtil.endSection(); decoderCounters.renderedOutputBufferCount++; consecutiveDroppedFrameCount = 0; if (!videoFrameProcessorManager.isEnabled()) { lastRenderRealtimeUs = SystemClock.elapsedRealtime() * 1000; maybeNotifyVideoSizeChanged(decodedVideoSize); maybeNotifyRenderedFirstFrame(); } } private boolean shouldUsePlaceholderSurface(MediaCodecInfo codecInfo) { return Util.SDK_INT >= 23 && !tunneling && !codecNeedsSetOutputSurfaceWorkaround(codecInfo.name) && (!codecInfo.secure || PlaceholderSurface.isSecureSupported(context)); } @RequiresApi(17) private void releasePlaceholderSurface() { if (displaySurface == placeholderSurface) { displaySurface = null; } placeholderSurface.release(); placeholderSurface = null; } private void setJoiningDeadlineMs() { joiningDeadlineMs = allowedJoiningTimeMs > 0 ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; } private void clearRenderedFirstFrame() { renderedFirstFrameAfterReset = false; // The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for // non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and // OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and // above. if (Util.SDK_INT >= 23 && tunneling) { @Nullable MediaCodecAdapter codec = getCodec(); // If codec is null then the listener will be instantiated in configureCodec. if (codec != null) { tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); } } } /* package */ void maybeNotifyRenderedFirstFrame() { renderedFirstFrameAfterEnable = true; if (!renderedFirstFrameAfterReset) { renderedFirstFrameAfterReset = true; eventDispatcher.renderedFirstFrame(displaySurface); haveReportedFirstFrameRenderedForCurrentSurface = true; } } private void maybeRenotifyRenderedFirstFrame() { if (haveReportedFirstFrameRenderedForCurrentSurface) { eventDispatcher.renderedFirstFrame(displaySurface); } } private void clearReportedVideoSize() { reportedVideoSize = null; } /** Notifies the new video size. */ private void maybeNotifyVideoSizeChanged(VideoSize newOutputSize) { if (!newOutputSize.equals(VideoSize.UNKNOWN) && !newOutputSize.equals(reportedVideoSize)) { reportedVideoSize = newOutputSize; eventDispatcher.videoSizeChanged(reportedVideoSize); } } private void maybeRenotifyVideoSizeChanged() { if (reportedVideoSize != null) { eventDispatcher.videoSizeChanged(reportedVideoSize); } } private void maybeNotifyDroppedFrames() { if (droppedFrames > 0) { long now = SystemClock.elapsedRealtime(); long elapsedMs = now - droppedFrameAccumulationStartTimeMs; eventDispatcher.droppedFrames(droppedFrames, elapsedMs); droppedFrames = 0; droppedFrameAccumulationStartTimeMs = now; } } private void maybeNotifyVideoFrameProcessingOffset() { if (videoFrameProcessingOffsetCount != 0) { eventDispatcher.reportVideoFrameProcessingOffset( totalVideoFrameProcessingOffsetUs, videoFrameProcessingOffsetCount); totalVideoFrameProcessingOffsetUs = 0; videoFrameProcessingOffsetCount = 0; } } private static boolean isBufferLate(long earlyUs) { // Class a buffer as late if it should have been presented more than 30 ms ago. return earlyUs < -30000; } private static boolean isBufferVeryLate(long earlyUs) { // Class a buffer as very late if it should have been presented more than 500 ms ago. return earlyUs < -500000; } @RequiresApi(29) private static void setHdr10PlusInfoV29(MediaCodecAdapter codec, byte[] hdr10PlusInfo) { Bundle codecParameters = new Bundle(); codecParameters.putByteArray(MediaCodec.PARAMETER_KEY_HDR10_PLUS_INFO, hdr10PlusInfo); codec.setParameters(codecParameters); } @RequiresApi(23) protected void setOutputSurfaceV23(MediaCodecAdapter codec, Surface surface) { codec.setOutputSurface(surface); } @RequiresApi(21) private static void configureTunnelingV21(MediaFormat mediaFormat, int tunnelingAudioSessionId) { mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId); } /** * Returns the framework {@link MediaFormat} that should be used to configure the decoder. * * @param format The {@link Format} of media. * @param codecMimeType The MIME type handled by the codec. * @param codecMaxValues Codec max values that should be used when configuring the decoder. * @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if * no codec operating rate should be set. * @param deviceNeedsNoPostProcessWorkaround Whether the device is known to do post processing by * default that isn't compatible with ExoPlayer. * @param tunnelingAudioSessionId The audio session id to use for tunneling, or {@link * C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. * @return The framework {@link MediaFormat} that should be used to configure the decoder. */ @SuppressLint(""InlinedApi"") @TargetApi(21) // tunnelingAudioSessionId is unset if Util.SDK_INT < 21 protected MediaFormat getMediaFormat( Format format, String codecMimeType, CodecMaxValues codecMaxValues, float codecOperatingRate, boolean deviceNeedsNoPostProcessWorkaround, int tunnelingAudioSessionId) { MediaFormat mediaFormat = new MediaFormat(); // Set format parameters that should always be set. mediaFormat.setString(MediaFormat.KEY_MIME, codecMimeType); mediaFormat.setInteger(MediaFormat.KEY_WIDTH, format.width); mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, format.height); MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); // Set format parameters that may be unset. MediaFormatUtil.maybeSetFloat(mediaFormat, MediaFormat.KEY_FRAME_RATE, format.frameRate); MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees); MediaFormatUtil.maybeSetColorInfo(mediaFormat, format.colorInfo); if (MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)) { // Some phones require the profile to be set on the codec. // See https://github.com/google/ExoPlayer/pull/5438. Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); if (codecProfileAndLevel != null) { MediaFormatUtil.maybeSetInteger( mediaFormat, MediaFormat.KEY_PROFILE, codecProfileAndLevel.first); } } // Set codec max values. mediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width); mediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, codecMaxValues.height); MediaFormatUtil.maybeSetInteger( mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, codecMaxValues.inputSize); // Set codec configuration values. if (Util.SDK_INT >= 23) { mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */); if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET) { mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate); } } if (deviceNeedsNoPostProcessWorkaround) { mediaFormat.setInteger(""no-post-process"", 1); mediaFormat.setInteger(""auto-frc"", 0); } if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { configureTunnelingV21(mediaFormat, tunnelingAudioSessionId); } return mediaFormat; } /** * Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way * that will allow possible adaptation to other compatible formats in {@code streamFormats}. * * @param codecInfo Information about the {@link MediaCodec} being configured. * @param format The {@link Format} for which the codec is being configured. * @param streamFormats The possible stream formats. * @return Suitable {@link CodecMaxValues}. */ protected CodecMaxValues getCodecMaxValues( MediaCodecInfo codecInfo, Format format, Format[] streamFormats) { int maxWidth = format.width; int maxHeight = format.height; int maxInputSize = getMaxInputSize(codecInfo, format); if (streamFormats.length == 1) { // The single entry in streamFormats must correspond to the format for which the codec is // being configured. if (maxInputSize != Format.NO_VALUE) { int codecMaxInputSize = getCodecMaxInputSize(codecInfo, format); if (codecMaxInputSize != Format.NO_VALUE) { // Scale up the initial video decoder maximum input size so playlist item transitions with // small increases in maximum sample size don't require reinitialization. This only makes // a difference if the exact maximum sample sizes are known from the container. int scaledMaxInputSize = (int) (maxInputSize * INITIAL_FORMAT_MAX_INPUT_SIZE_SCALE_FACTOR); // Avoid exceeding the maximum expected for the codec. maxInputSize = min(scaledMaxInputSize, codecMaxInputSize); } } return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); } boolean haveUnknownDimensions = false; for (Format streamFormat : streamFormats) { if (format.colorInfo != null && streamFormat.colorInfo == null) { // streamFormat likely has incomplete color information. Copy the complete color information // from format to avoid codec re-use being ruled out for only this reason. streamFormat = streamFormat.buildUpon().setColorInfo(format.colorInfo).build(); } if (codecInfo.canReuseCodec(format, streamFormat).result != REUSE_RESULT_NO) { haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); maxWidth = max(maxWidth, streamFormat.width); maxHeight = max(maxHeight, streamFormat.height); maxInputSize = max(maxInputSize, getMaxInputSize(codecInfo, streamFormat)); } } if (haveUnknownDimensions) { Log.w(TAG, ""Resolutions unknown. Codec max resolution: "" + maxWidth + ""x"" + maxHeight); @Nullable Point codecMaxSize = getCodecMaxSize(codecInfo, format); if (codecMaxSize != null) { maxWidth = max(maxWidth, codecMaxSize.x); maxHeight = max(maxHeight, codecMaxSize.y); maxInputSize = max( maxInputSize, getCodecMaxInputSize( codecInfo, format.buildUpon().setWidth(maxWidth).setHeight(maxHeight).build())); Log.w(TAG, ""Codec max resolution adjusted to: "" + maxWidth + ""x"" + maxHeight); } } return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); } @Override protected MediaCodecDecoderException createDecoderException( Throwable cause, @Nullable MediaCodecInfo codecInfo) { return new MediaCodecVideoDecoderException(cause, codecInfo, displaySurface); } /** Manages {@link VideoFrameProcessor} interactions. */ private static final class VideoFrameProcessorManager { /** The threshold for releasing a processed frame. */ private static final long EARLY_THRESHOLD_US = 50_000; private final VideoFrameReleaseHelper frameReleaseHelper; // TODO(b/238302341) Consider removing the reference to the containing class and make this class // non-static. private final MediaCodecVideoRenderer renderer; private final ArrayDeque processedFramesTimestampsUs; private final ArrayDeque> pendingFrameFormats; private @MonotonicNonNull Handler handler; @Nullable private VideoFrameProcessor videoFrameProcessor; @Nullable private CopyOnWriteArrayList videoEffects; @Nullable private Format inputFormat; /** * The current frame {@link Format} and the earliest presentationTimeUs that associates to it. */ private @MonotonicNonNull Pair currentFrameFormat; @Nullable private Pair currentSurfaceAndSize; private int videoFrameProcessorMaxPendingFrameCount; private boolean canEnableFrameProcessing; /** * Whether the last frame of the current stream is decoded and registered to {@link * VideoFrameProcessor}. */ private boolean registeredLastFrame; /** * Whether the last frame of the current stream is processed by the {@link VideoFrameProcessor}. */ private boolean processedLastFrame; /** Whether the last frame of the current stream is released to the output {@link Surface}. */ private boolean releasedLastFrame; private long lastCodecBufferPresentationTimestampUs; private VideoSize processedFrameSize; private boolean pendingOutputSizeChange; /** The presentation time, after which the listener should be notified about the size change. */ private long pendingOutputSizeChangeNotificationTimeUs; private long initialStreamOffsetUs; /** Creates a new instance. */ public VideoFrameProcessorManager( VideoFrameReleaseHelper frameReleaseHelper, @UnderInitialization MediaCodecVideoRenderer renderer) { this.frameReleaseHelper = frameReleaseHelper; this.renderer = renderer; processedFramesTimestampsUs = new ArrayDeque<>(); pendingFrameFormats = new ArrayDeque<>(); videoFrameProcessorMaxPendingFrameCount = C.LENGTH_UNSET; canEnableFrameProcessing = true; lastCodecBufferPresentationTimestampUs = C.TIME_UNSET; processedFrameSize = VideoSize.UNKNOWN; pendingOutputSizeChangeNotificationTimeUs = C.TIME_UNSET; initialStreamOffsetUs = C.TIME_UNSET; } /** Sets the {@linkplain Effect video effects}. */ public void setVideoEffects(List videoEffects) { if (this.videoEffects == null) { this.videoEffects = new CopyOnWriteArrayList<>(videoEffects); return; } this.videoEffects.clear(); this.videoEffects.addAll(videoEffects); } /** Returns whether video frame processing is enabled. */ public boolean isEnabled() { return videoFrameProcessor != null; } /** Returns whether {@code VideoFrameProcessorManager} is ready to accept input frames. */ public boolean isReady() { return currentSurfaceAndSize == null || !currentSurfaceAndSize.second.equals(Size.UNKNOWN); } /** * Whether the {@link VideoFrameProcessor} has released the last frame in the current stream. */ public boolean releasedLastFrame() { return releasedLastFrame; } /** * Flushes the {@link VideoFrameProcessor}. * *

Caller must ensure video frame processing {@linkplain #isEnabled() is enabled} before * calling this method. */ public void flush() { checkStateNotNull(videoFrameProcessor); videoFrameProcessor.flush(); processedFramesTimestampsUs.clear(); handler.removeCallbacksAndMessages(/* token= */ null); if (registeredLastFrame) { registeredLastFrame = false; processedLastFrame = false; releasedLastFrame = false; } } /** * Tries to enable video frame processing. * *

Caller must ensure video frame processing {@linkplain #isEnabled() is not enabled} before * calling this method. * * @param inputFormat The {@link Format} that is input into the {@link VideoFrameProcessor}. * @return Whether video frame processing is enabled. * @throws ExoPlaybackException When enabling the {@link VideoFrameProcessor} failed. */ @CanIgnoreReturnValue public boolean maybeEnable(Format inputFormat, long initialStreamOffsetUs) throws ExoPlaybackException { checkState(!isEnabled()); if (!canEnableFrameProcessing) { return false; } if (videoEffects == null) { canEnableFrameProcessing = false; return false; } // Playback thread handler. handler = Util.createHandlerForCurrentLooper(); Pair inputAndOutputColorInfos = renderer.experimentalGetVideoFrameProcessorColorConfiguration(inputFormat.colorInfo); try { // TODO(b/243036513): Set rotation in setInputFormat() after supporting changing effects. if (!codecAppliesRotation() && inputFormat.rotationDegrees != 0) { // Insert as the first effect as if the decoder has applied the rotation. videoEffects.add( /* index= */ 0, VideoFrameProcessorAccessor.createRotationEffect(inputFormat.rotationDegrees)); } videoFrameProcessor = VideoFrameProcessorAccessor.getFrameProcessorFactory() .create( renderer.context, checkNotNull(videoEffects), DebugViewProvider.NONE, inputAndOutputColorInfos.first, inputAndOutputColorInfos.second, /* renderFramesAutomatically= */ false, /* listenerExecutor= */ handler::post, new VideoFrameProcessor.Listener() { @Override public void onOutputSizeChanged(int width, int height) { @Nullable Format inputFormat = VideoFrameProcessorManager.this.inputFormat; checkStateNotNull(inputFormat); // TODO(b/264889146): Handle Effect that changes output size based on pts. processedFrameSize = new VideoSize( width, height, // VideoFrameProcessor is configured to produce rotation free // frames. /* unappliedRotationDegrees= */ 0, // VideoFrameProcessor always outputs pixelWidthHeightRatio 1. /* pixelWidthHeightRatio= */ 1.f); pendingOutputSizeChange = true; } @Override public void onOutputFrameAvailableForRendering(long presentationTimeUs) { if (registeredLastFrame) { checkState(lastCodecBufferPresentationTimestampUs != C.TIME_UNSET); } processedFramesTimestampsUs.add(presentationTimeUs); // TODO(b/257464707) Support extensively modified media. if (registeredLastFrame && presentationTimeUs >= lastCodecBufferPresentationTimestampUs) { processedLastFrame = true; } if (pendingOutputSizeChange) { // Report the size change on releasing this frame. pendingOutputSizeChange = false; pendingOutputSizeChangeNotificationTimeUs = presentationTimeUs; } } @Override public void onError(VideoFrameProcessingException exception) { renderer.setPendingPlaybackException( renderer.createRendererException( exception, inputFormat, PlaybackException.ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED)); } @Override public void onEnded() { throw new IllegalStateException(); } }); videoFrameProcessor.registerInputStream(VideoFrameProcessor.INPUT_TYPE_SURFACE); this.initialStreamOffsetUs = initialStreamOffsetUs; } catch (Exception e) { throw renderer.createRendererException( e, inputFormat, PlaybackException.ERROR_CODE_VIDEO_FRAME_PROCESSOR_INIT_FAILED); } if (currentSurfaceAndSize != null) { Size outputSurfaceSize = currentSurfaceAndSize.second; videoFrameProcessor.setOutputSurfaceInfo( new SurfaceInfo( currentSurfaceAndSize.first, outputSurfaceSize.getWidth(), outputSurfaceSize.getHeight())); } setInputFormat(inputFormat); return true; } public long getCorrectedFramePresentationTimeUs( long framePresentationTimeUs, long currentStreamOffsetUs) { // VideoFrameProcessor takes in frames with monotonically increasing, non-offset frame // timestamps. That is, with two ten-second long videos, the first frame of the second video // should bear a timestamp of 10s seen from VideoFrameProcessor; while in ExoPlayer, the // timestamp of the said frame would be 0s, but the streamOffset is incremented 10s to include // the duration of the first video. Thus this correction is need to correct for the different // handling of presentation timestamps in ExoPlayer and VideoFrameProcessor. checkState(initialStreamOffsetUs != C.TIME_UNSET); return framePresentationTimeUs + currentStreamOffsetUs - initialStreamOffsetUs; } /** * Returns the {@linkplain VideoFrameProcessor#getInputSurface input surface} of the {@link * VideoFrameProcessor}. * *

Caller must ensure the {@code VideoFrameProcessorManager} {@link #isEnabled()} before * calling this method. */ public Surface getInputSurface() { return checkNotNull(videoFrameProcessor).getInputSurface(); } /** * Sets the output surface info. * * @param outputSurface The {@link Surface} to which {@link VideoFrameProcessor} outputs. * @param outputResolution The {@link Size} of the output resolution. */ public void setOutputSurfaceInfo(Surface outputSurface, Size outputResolution) { if (currentSurfaceAndSize != null && currentSurfaceAndSize.first.equals(outputSurface) && currentSurfaceAndSize.second.equals(outputResolution)) { return; } currentSurfaceAndSize = Pair.create(outputSurface, outputResolution); if (isEnabled()) { checkNotNull(videoFrameProcessor) .setOutputSurfaceInfo( new SurfaceInfo( outputSurface, outputResolution.getWidth(), outputResolution.getHeight())); } } /** * Clears the set output surface info. * *

Caller must ensure the {@code VideoFrameProcessorManager} {@link #isEnabled()} before * calling this method. */ public void clearOutputSurfaceInfo() { checkNotNull(videoFrameProcessor).setOutputSurfaceInfo(null); currentSurfaceAndSize = null; } /** * Sets the input surface info. * *

Caller must ensure the {@code VideoFrameProcessorManager} {@link #isEnabled()} before * calling this method. */ public void setInputFormat(Format inputFormat) { checkNotNull(videoFrameProcessor) .setInputFrameInfo( new FrameInfo.Builder(inputFormat.width, inputFormat.height) .setPixelWidthHeightRatio(inputFormat.pixelWidthHeightRatio) .build()); this.inputFormat = inputFormat; if (registeredLastFrame) { registeredLastFrame = false; processedLastFrame = false; releasedLastFrame = false; } } /** Sets the necessary {@link MediaFormat} keys for video frame processing. */ @SuppressWarnings(""InlinedApi"") public MediaFormat amendMediaFormatKeys(MediaFormat mediaFormat) { if (Util.SDK_INT >= 29 && renderer.context.getApplicationContext().getApplicationInfo().targetSdkVersion >= 29) { mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0); } return mediaFormat; } /** * Must be called when the codec is initialized. * *

Sets the {@code videoFrameProcessorMaxPendingFrameCount} based on the {@code codecName}. */ public void onCodecInitialized(String codecName) { videoFrameProcessorMaxPendingFrameCount = Util.getMaxPendingFramesCountForMediaCodecDecoders( renderer.context, codecName, /* requestedHdrToneMapping= */ false); } /** * Tries to {@linkplain VideoFrameProcessor#registerInputFrame register an input frame}. * *

Caller must ensure the {@code VideoFrameProcessorManager} {@link #isEnabled()} before * calling this method. * * @param format The {@link Format} associated with the frame. * @param isLastBuffer Whether the buffer is the last from the decoder to register. * @return Whether {@link MediaCodec} should render the frame to {@link VideoFrameProcessor}. */ public boolean maybeRegisterFrame( Format format, long presentationTimestampUs, boolean isLastBuffer) { checkStateNotNull(videoFrameProcessor); checkState(videoFrameProcessorMaxPendingFrameCount != C.LENGTH_UNSET); if (videoFrameProcessor.getPendingInputFrameCount() < videoFrameProcessorMaxPendingFrameCount) { videoFrameProcessor.registerInputFrame(); if (currentFrameFormat == null) { currentFrameFormat = Pair.create(presentationTimestampUs, format); } else if (!Util.areEqual(format, currentFrameFormat.second)) { // TODO(b/258213806) Remove format comparison for better performance. pendingFrameFormats.add(Pair.create(presentationTimestampUs, format)); } if (isLastBuffer) { registeredLastFrame = true; lastCodecBufferPresentationTimestampUs = presentationTimestampUs; } return true; } return false; } /** * Releases the processed frames to the {@linkplain #setOutputSurfaceInfo output surface}. * *

Caller must ensure the {@code VideoFrameProcessorManager} {@link #isEnabled()} before * calling this method. */ public void releaseProcessedFrames(long [MASK] , long elapsedRealtimeUs) { checkStateNotNull(videoFrameProcessor); while (!processedFramesTimestampsUs.isEmpty()) { boolean isStarted = renderer.getState() == STATE_STARTED; long framePresentationTimeUs = checkNotNull(processedFramesTimestampsUs.peek()); long bufferPresentationTimeUs = framePresentationTimeUs + initialStreamOffsetUs; long earlyUs = renderer.calculateEarlyTimeUs( [MASK] , elapsedRealtimeUs, SystemClock.elapsedRealtime() * 1000, bufferPresentationTimeUs, isStarted); boolean isLastFrame = processedLastFrame && processedFramesTimestampsUs.size() == 1; boolean shouldReleaseFrameImmediately = renderer.shouldForceRender( [MASK] , earlyUs); if (shouldReleaseFrameImmediately) { releaseProcessedFrameInternal( VideoFrameProcessor.RENDER_OUTPUT_FRAME_IMMEDIATELY, isLastFrame); break; } else if (!isStarted || [MASK] == renderer.initialPositionUs) { return; } // Only release frames that are reasonably close to presentation. // This way frameReleaseHelper.onNextFrame() is called only once for each frame. if (earlyUs > EARLY_THRESHOLD_US) { break; } frameReleaseHelper.onNextFrame(bufferPresentationTimeUs); long unadjustedFrameReleaseTimeNs = System.nanoTime() + earlyUs * 1000; long adjustedFrameReleaseTimeNs = frameReleaseHelper.adjustReleaseTime(unadjustedFrameReleaseTimeNs); earlyUs = (adjustedFrameReleaseTimeNs - System.nanoTime()) / 1000; // TODO(b/238302341) Handle very late buffers and drop to key frame. Need to flush // VideoFrameProcessor input frames in this case. if (renderer.shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs, isLastFrame)) { releaseProcessedFrameInternal(VideoFrameProcessor.DROP_OUTPUT_FRAME, isLastFrame); continue; } if (!pendingFrameFormats.isEmpty() && bufferPresentationTimeUs > pendingFrameFormats.peek().first) { currentFrameFormat = pendingFrameFormats.remove(); } renderer.notifyFrameMetadataListener( framePresentationTimeUs, adjustedFrameReleaseTimeNs, currentFrameFormat.second); if (pendingOutputSizeChangeNotificationTimeUs >= bufferPresentationTimeUs) { pendingOutputSizeChangeNotificationTimeUs = C.TIME_UNSET; renderer.maybeNotifyVideoSizeChanged(processedFrameSize); } releaseProcessedFrameInternal(adjustedFrameReleaseTimeNs, isLastFrame); } } /** * Releases the resources. * *

Caller must ensure video frame processing {@linkplain #isEnabled() is not enabled} before * calling this method. */ public void reset() { checkNotNull(videoFrameProcessor).release(); videoFrameProcessor = null; if (handler != null) { handler.removeCallbacksAndMessages(/* token= */ null); } if (videoEffects != null) { videoEffects.clear(); } processedFramesTimestampsUs.clear(); canEnableFrameProcessing = true; } private void releaseProcessedFrameInternal(long releaseTimeNs, boolean isLastFrame) { // VideoFrameProcessor renders to its output surface using // VideoFrameProcessor.renderOutputFrame, to release the MediaCodecVideoRenderer frame. checkStateNotNull(videoFrameProcessor); videoFrameProcessor.renderOutputFrame(releaseTimeNs); processedFramesTimestampsUs.remove(); renderer.lastRenderRealtimeUs = SystemClock.elapsedRealtime() * 1000; if (releaseTimeNs != VideoFrameProcessor.DROP_OUTPUT_FRAME) { renderer.maybeNotifyRenderedFirstFrame(); } if (isLastFrame) { releasedLastFrame = true; } } private static final class VideoFrameProcessorAccessor { private static @MonotonicNonNull Constructor scaleAndRotateTransformationBuilderConstructor; private static @MonotonicNonNull Method setRotationMethod; private static @MonotonicNonNull Method buildScaleAndRotateTransformationMethod; private static @MonotonicNonNull Constructor videoFrameProcessorFactoryBuilderConstructor; private static @MonotonicNonNull Method buildVideoFrameProcessorFactoryMethod; public static Effect createRotationEffect(float rotationDegrees) throws Exception { prepare(); Object builder = scaleAndRotateTransformationBuilderConstructor.newInstance(); setRotationMethod.invoke(builder, rotationDegrees); return (Effect) checkNotNull(buildScaleAndRotateTransformationMethod.invoke(builder)); } public static VideoFrameProcessor.Factory getFrameProcessorFactory() throws Exception { prepare(); Object builder = videoFrameProcessorFactoryBuilderConstructor.newInstance(); return (VideoFrameProcessor.Factory) checkNotNull(buildVideoFrameProcessorFactoryMethod.invoke(builder)); } @EnsuresNonNull({ ""scaleAndRotateTransformationBuilderConstructor"", ""setRotationMethod"", ""buildScaleAndRotateTransformationMethod"", ""videoFrameProcessorFactoryBuilderConstructor"", ""buildVideoFrameProcessorFactoryMethod"" }) private static void prepare() throws Exception { if (scaleAndRotateTransformationBuilderConstructor == null || setRotationMethod == null || buildScaleAndRotateTransformationMethod == null) { Class scaleAndRotateTransformationBuilderClass = Class.forName( ""com.google.android.exoplayer2.effect.ScaleAndRotateTransformation$Builder""); scaleAndRotateTransformationBuilderConstructor = scaleAndRotateTransformationBuilderClass.getConstructor(); setRotationMethod = scaleAndRotateTransformationBuilderClass.getMethod(""setRotationDegrees"", float.class); buildScaleAndRotateTransformationMethod = scaleAndRotateTransformationBuilderClass.getMethod(""build""); } if (videoFrameProcessorFactoryBuilderConstructor == null || buildVideoFrameProcessorFactoryMethod == null) { Class videoFrameProcessorFactoryBuilderClass = Class.forName( ""com.google.android.exoplayer2.effect.DefaultVideoFrameProcessor$Factory$Builder""); videoFrameProcessorFactoryBuilderConstructor = videoFrameProcessorFactoryBuilderClass.getConstructor(); buildVideoFrameProcessorFactoryMethod = videoFrameProcessorFactoryBuilderClass.getMethod(""build""); } } } } /** * Returns a maximum video size to use when configuring a codec for {@code format} in a way that * will allow possible adaptation to other compatible formats that are expected to have the same * aspect ratio, but whose sizes are unknown. * * @param codecInfo Information about the {@link MediaCodec} being configured. * @param format The {@link Format} for which the codec is being configured. * @return The maximum video size to use, or {@code null} if the size of {@code format} should be * used. */ @Nullable private static Point getCodecMaxSize(MediaCodecInfo codecInfo, Format format) { boolean isVerticalVideo = format.height > format.width; int formatLongEdgePx = isVerticalVideo ? format.height : format.width; int formatShortEdgePx = isVerticalVideo ? format.width : format.height; float aspectRatio = (float) formatShortEdgePx / formatLongEdgePx; for (int longEdgePx : STANDARD_LONG_EDGE_VIDEO_PX) { int shortEdgePx = (int) (longEdgePx * aspectRatio); if (longEdgePx <= formatLongEdgePx || shortEdgePx <= formatShortEdgePx) { // Don't return a size not larger than the format for which the codec is being configured. return null; } else if (Util.SDK_INT >= 21) { Point alignedSize = codecInfo.alignVideoSizeV21( isVerticalVideo ? shortEdgePx : longEdgePx, isVerticalVideo ? longEdgePx : shortEdgePx); float frameRate = format.frameRate; if (codecInfo.isVideoSizeAndRateSupportedV21(alignedSize.x, alignedSize.y, frameRate)) { return alignedSize; } } else { try { // Conservatively assume the codec requires 16px width and height alignment. longEdgePx = Util.ceilDivide(longEdgePx, 16) * 16; shortEdgePx = Util.ceilDivide(shortEdgePx, 16) * 16; if (longEdgePx * shortEdgePx <= MediaCodecUtil.maxH264DecodableFrameSize()) { return new Point( isVerticalVideo ? shortEdgePx : longEdgePx, isVerticalVideo ? longEdgePx : shortEdgePx); } } catch (DecoderQueryException e) { // We tried our best. Give up! return null; } } } return null; } /** * Returns a maximum input buffer size for a given {@link MediaCodec} and {@link Format}. * * @param codecInfo Information about the {@link MediaCodec} being configured. * @param format The format. * @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not * be determined. */ protected static int getMaxInputSize(MediaCodecInfo codecInfo, Format format) { if (format.maxInputSize != Format.NO_VALUE) { // The format defines an explicit maximum input size. Add the total size of initialization // data buffers, as they may need to be queued in the same input buffer as the largest sample. int totalInitializationDataSize = 0; int initializationDataCount = format.initializationData.size(); for (int i = 0; i < initializationDataCount; i++) { totalInitializationDataSize += format.initializationData.get(i).length; } return format.maxInputSize + totalInitializationDataSize; } else { return getCodecMaxInputSize(codecInfo, format); } } private static boolean codecAppliesRotation() { return Util.SDK_INT >= 21; } /** * Returns whether the device is known to do post processing by default that isn't compatible with * ExoPlayer. * * @return Whether the device is known to do post processing by default that isn't compatible with * ExoPlayer. */ private static boolean deviceNeedsNoPostProcessWorkaround() { // Nvidia devices prior to M try to adjust the playback rate to better map the frame-rate of // content to the refresh rate of the display. For example playback of 23.976fps content is // adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the // implementation causes ExoPlayer's reported playback position to drift out of sync. Captions // also lose sync [Internal: b/26453592]. Even after M, the devices may apply post processing // operations that can modify frame output timestamps, which is incompatible with ExoPlayer's // logic for skipping decode-only frames. return ""NVIDIA"".equals(Util.MANUFACTURER); } /* * TODO: * * 1. Validate that Android device certification now ensures correct behavior, and add a * corresponding SDK_INT upper bound for applying the workaround (probably SDK_INT < 26). * 2. Determine a complete list of affected devices. * 3. Some of the devices in this list only fail to support setOutputSurface when switching from * a SurfaceView provided Surface to a Surface of another type (e.g. TextureView/DummySurface), * and vice versa. One hypothesis is that setOutputSurface fails when the surfaces have * different pixel formats. If we can find a way to query the Surface instances to determine * whether this case applies, then we'll be able to provide a more targeted workaround. */ /** * Returns whether the codec is known to implement {@link MediaCodec#setOutputSurface(Surface)} * incorrectly. * *

If true is returned then we fall back to releasing and re-instantiating the codec instead. * * @param name The name of the codec. * @return True if the device is known to implement {@link MediaCodec#setOutputSurface(Surface)} * incorrectly. */ protected boolean codecNeedsSetOutputSurfaceWorkaround(String name) { if (name.startsWith(""OMX.google"")) { // Google OMX decoders are not known to have this issue on any API level. return false; } synchronized (MediaCodecVideoRenderer.class) { if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) { deviceNeedsSetOutputSurfaceWorkaround = evaluateDeviceNeedsSetOutputSurfaceWorkaround(); evaluatedDeviceNeedsSetOutputSurfaceWorkaround = true; } } return deviceNeedsSetOutputSurfaceWorkaround; } /** Returns the output surface. */ @Nullable protected Surface getSurface() { // TODO(b/260702159) Consider renaming the method to getOutputSurface(). return displaySurface; } protected static final class CodecMaxValues { public final int width; public final int height; public final int inputSize; public CodecMaxValues(int width, int height, int inputSize) { this.width = width; this.height = height; this.inputSize = inputSize; } } /** * Returns the maximum sample size assuming three channel 4:2:0 subsampled input frames with the * specified {@code minCompressionRatio} * * @param pixelCount The number of pixels * @param minCompressionRatio The minimum compression ratio */ private static int getMaxSampleSize(int pixelCount, int minCompressionRatio) { return (pixelCount * 3) / (2 * minCompressionRatio); } private static boolean evaluateDeviceNeedsSetOutputSurfaceWorkaround() { if (Util.SDK_INT <= 28) { // Workaround for MiTV and MiBox devices which have been observed broken up to API 28. // https://github.com/google/ExoPlayer/issues/5169, // https://github.com/google/ExoPlayer/issues/6899. // https://github.com/google/ExoPlayer/issues/8014. // https://github.com/google/ExoPlayer/issues/8329. // https://github.com/google/ExoPlayer/issues/9710. switch (Util.DEVICE) { case ""aquaman"": case ""dangal"": case ""dangalUHD"": case ""dangalFHD"": case ""magnolia"": case ""machuca"": case ""once"": case ""oneday"": return true; default: break; // Do nothing. } } if (Util.SDK_INT <= 27 && ""HWEML"".equals(Util.DEVICE)) { // Workaround for Huawei P20: // https://github.com/google/ExoPlayer/issues/4468#issuecomment-459291645. return true; } switch (Util.MODEL) { // Workaround for some Fire OS devices. case ""AFTA"": case ""AFTN"": case ""AFTR"": case ""AFTEU011"": case ""AFTEU014"": case ""AFTEUFF014"": case ""AFTJMST12"": case ""AFTKMST12"": case ""AFTSO001"": return true; default: break; // Do nothing. } if (Util.SDK_INT <= 26) { // In general, devices running API level 27 or later should be unaffected unless observed // otherwise. Enable the workaround on a per-device basis. Works around: // https://github.com/google/ExoPlayer/issues/3236, // https://github.com/google/ExoPlayer/issues/3355, // https://github.com/google/ExoPlayer/issues/3439, // https://github.com/google/ExoPlayer/issues/3724, // https://github.com/google/ExoPlayer/issues/3835, // https://github.com/google/ExoPlayer/issues/4006, // https://github.com/google/ExoPlayer/issues/4084, // https://github.com/google/ExoPlayer/issues/4104, // https://github.com/google/ExoPlayer/issues/4134, // https://github.com/google/ExoPlayer/issues/4315, // https://github.com/google/ExoPlayer/issues/4419, // https://github.com/google/ExoPlayer/issues/4460, // https://github.com/google/ExoPlayer/issues/4468, // https://github.com/google/ExoPlayer/issues/5312, // https://github.com/google/ExoPlayer/issues/6503. // https://github.com/google/ExoPlayer/issues/8014, // https://github.com/google/ExoPlayer/pull/8030. switch (Util.DEVICE) { case ""1601"": case ""1713"": case ""1714"": case ""601LV"": case ""602LV"": case ""A10-70F"": case ""A10-70L"": case ""A1601"": case ""A2016a40"": case ""A7000-a"": case ""A7000plus"": case ""A7010a48"": case ""A7020a48"": case ""AquaPowerM"": case ""ASUS_X00AD_2"": case ""Aura_Note_2"": case ""b5"": case ""BLACK-1X"": case ""BRAVIA_ATV2"": case ""BRAVIA_ATV3_4K"": case ""C1"": case ""ComioS1"": case ""CP8676_I02"": case ""CPH1609"": case ""CPH1715"": case ""CPY83_I00"": case ""cv1"": case ""cv3"": case ""deb"": case ""DM-01K"": case ""E5643"": case ""ELUGA_A3_Pro"": case ""ELUGA_Note"": case ""ELUGA_Prim"": case ""ELUGA_Ray_X"": case ""EverStar_S"": case ""F01H"": case ""F01J"": case ""F02H"": case ""F03H"": case ""F04H"": case ""F04J"": case ""F3111"": case ""F3113"": case ""F3116"": case ""F3211"": case ""F3213"": case ""F3215"": case ""F3311"": case ""flo"": case ""fugu"": case ""GiONEE_CBL7513"": case ""GiONEE_GBL7319"": case ""GIONEE_GBL7360"": case ""GIONEE_SWW1609"": case ""GIONEE_SWW1627"": case ""GIONEE_SWW1631"": case ""GIONEE_WBL5708"": case ""GIONEE_WBL7365"": case ""GIONEE_WBL7519"": case ""griffin"": case ""htc_e56ml_dtul"": case ""hwALE-H"": case ""HWBLN-H"": case ""HWCAM-H"": case ""HWVNS-H"": case ""HWWAS-H"": case ""i9031"": case ""iball8735_9806"": case ""Infinix-X572"": case ""iris60"": case ""itel_S41"": case ""j2xlteins"": case ""JGZ"": case ""K50a40"": case ""kate"": case ""l5460"": case ""le_x6"": case ""LS-5017"": case ""M04"": case ""M5c"": case ""manning"": case ""marino_f"": case ""MEIZU_M5"": case ""mh"": case ""mido"": case ""MX6"": case ""namath"": case ""nicklaus_f"": case ""NX541J"": case ""NX573J"": case ""OnePlus5T"": case ""p212"": case ""P681"": case ""P85"": case ""pacificrim"": case ""panell_d"": case ""panell_dl"": case ""panell_ds"": case ""panell_dt"": case ""PB2-670M"": case ""PGN528"": case ""PGN610"": case ""PGN611"": case ""Phantom6"": case ""Pixi4-7_3G"": case ""Pixi5-10_4G"": case ""PLE"": case ""PRO7S"": case ""Q350"": case ""Q4260"": case ""Q427"": case ""Q4310"": case ""Q5"": case ""QM16XE_U"": case ""QX1"": case ""RAIJIN"": case ""santoni"": case ""Slate_Pro"": case ""SVP-DTV15"": case ""s905x018"": case ""taido_row"": case ""TB3-730F"": case ""TB3-730X"": case ""TB3-850F"": case ""TB3-850M"": case ""tcl_eu"": case ""V1"": case ""V23GB"": case ""V5"": case ""vernee_M5"": case ""watson"": case ""whyred"": case ""woods_f"": case ""woods_fn"": case ""X3_HK"": case ""XE2X"": case ""XT1663"": case ""Z12_PRO"": case ""Z80"": return true; default: break; // Do nothing. } switch (Util.MODEL) { case ""JSN-L21"": return true; default: break; // Do nothing. } } return false; } @RequiresApi(23) private final class OnFrameRenderedListenerV23 implements MediaCodecAdapter.OnFrameRenderedListener, Handler.Callback { private static final int HANDLE_FRAME_RENDERED = 0; private final Handler handler; public OnFrameRenderedListenerV23(MediaCodecAdapter codec) { handler = Util.createHandlerForCurrentLooper(/* callback= */ this); codec.setOnFrameRenderedListener(/* listener= */ this, handler); } @Override public void onFrameRendered(MediaCodecAdapter codec, long presentationTimeUs, long nanoTime) { // Workaround bug in MediaCodec that causes deadlock if you call directly back into the // MediaCodec from this listener method. // Deadlock occurs because MediaCodec calls this listener method holding a lock, // which may also be required by calls made back into the MediaCodec. // This was fixed in https://android-review.googlesource.com/1156807. // // The workaround queues the event for subsequent processing, where the lock will not be held. if (Util.SDK_INT < 30) { Message message = Message.obtain( handler, /* what= */ HANDLE_FRAME_RENDERED, /* arg1= */ (int) (presentationTimeUs >> 32), /* arg2= */ (int) presentationTimeUs); handler.sendMessageAtFrontOfQueue(message); } else { handleFrameRendered(presentationTimeUs); } } @Override public boolean handleMessage(Message message) { switch (message.what) { case HANDLE_FRAME_RENDERED: handleFrameRendered(Util.toLong(message.arg1, message.arg2)); return true; default: return false; } } private void handleFrameRendered(long presentationTimeUs) { if (this != tunnelingOnFrameRenderedListener || getCodec() == null) { // Stale event. return; } if (presentationTimeUs == TUNNELING_EOS_PRESENTATION_TIME_US) { onProcessedTunneledEndOfStream(); } else { try { onProcessedTunneledBuffer(presentationTimeUs); } catch (ExoPlaybackException e) { setPendingPlaybackException(e); } } } } } ","positionUs " "// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.skyframe; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Uninterruptibles; import com.google.devtools.build.lib.testutil.TestUtils; import com.google.devtools.build.lib.util.Pair; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Safely await {@link CountDownLatch}es in tests, storing any exceptions that happen. Callers * should call {@link #assertNoErrors} at the end of each test method, either manually or using an * {@code @After} hook. */ public class TrackingAwaiter { public static final TrackingAwaiter INSTANCE = new TrackingAwaiter(); private TrackingAwaiter() {} private final ConcurrentLinkedQueue> exceptionsThrown = new ConcurrentLinkedQueue<>(); /** * This method fixes a race condition with simply calling {@link CountDownLatch#await}. If this * thread is interrupted before {@code latch.await} is called, then {@code latch.await} will throw * an {@link InterruptedException} without checking the value of the latch at all. This leads to a * race condition in which this thread will throw an InterruptedException if it is slow calling * {@code latch.await}, but it will succeed normally otherwise. * *

To avoid this, we wait for the latch uninterruptibly. In the end, if the latch has in fact * been released, we do nothing, although the interrupted bit is set, so that the caller can * decide to throw an InterruptedException if it wants to. If the latch was not released, then * this was not a race condition, but an honest-to-goodness interrupt, and we propagate the * exception onward. */ private static void waitAndMaybeThrowInterrupt(CountDownLatch latch, String errorMessage) throws InterruptedException { if (Uninterruptibles.awaitUninterruptibly(latch, TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { // Latch was released. We can ignore the interrupt state. return; } if (!Thread.currentThread().isInterrupted()) { // Nobody interrupted us, but latch wasn't released. Failure. throw new AssertionError(errorMessage); } else { // We were interrupted before the latch was released. Propagate this interruption. throw new InterruptedException(); } } /** Threadpools can swallow exceptions. Make sure they don't get lost. */ public void awaitLatchAndTrackExceptions(CountDownLatch latch, String errorMessage) { try { waitAndMaybeThrowInterrupt(latch, errorMessage); } catch (Throwable e) { // We would expect e to be InterruptedException or AssertionError, but we leave it open so // that any throwable gets recorded. exceptionsThrown.add(Pair.of(errorMessage, e)); // Caller will assert exceptionsThrown is empty at end of test and fail, even if this is // swallowed. Throwables.propagate(e); } } /** Allow arbitrary errors to be recorded here for later throwing. */ public void injectExceptionAndMessage(Throwable throwable, String [MASK] ) { exceptionsThrown.add(Pair.of( [MASK] , throwable)); } public void assertNoErrors() { List> thisEvalExceptionsThrown = ImmutableList.copyOf(exceptionsThrown); exceptionsThrown.clear(); assertThat(thisEvalExceptionsThrown).isEmpty(); } } ","message " "// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.android; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.getFirstArtifactEndingWith; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.hasInput; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.prettyArtifactNames; import static com.google.devtools.build.lib.rules.java.JavaCompileActionTestHelper.getJavacArguments; import static com.google.devtools.build.lib.rules.java.JavaCompileActionTestHelper.getProcessorpath; import static org.junit.Assert.assertThrows; import com.google.common.base.Ascii; import com.google.common.base.Joiner; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.truth.Truth; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.FailAction; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.AnalysisResult; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.OutputGroupInfo; import com.google.devtools.build.lib.analysis.RequiredConfigFragmentsProvider; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.util.DummyTestFragment; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.FileTarget; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.util.BazelMockAndroidSupport; import com.google.devtools.build.lib.rules.android.AndroidBinaryTest.WithPlatforms; import com.google.devtools.build.lib.rules.android.AndroidBinaryTest.WithoutPlatforms; import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode; import com.google.devtools.build.lib.rules.android.deployinfo.AndroidDeployInfoOuterClass.AndroidDeployInfo; import com.google.devtools.build.lib.rules.cpp.CppFileTypes; import com.google.devtools.build.lib.rules.cpp.CppLinkAction; import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider; import com.google.devtools.build.lib.rules.java.JavaCompileAction; import com.google.devtools.build.lib.rules.java.JavaInfo; import com.google.devtools.build.lib.rules.java.JavaSemantics; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import com.google.devtools.build.lib.testutil.MoreAsserts; import com.google.devtools.build.lib.testutil.TestConstants; import com.google.devtools.build.lib.testutil.TestRuleClassProvider; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.function.Function; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** A test for {@link com.google.devtools.build.lib.rules.android.AndroidBinary}. */ @RunWith(Suite.class) @SuiteClasses({WithoutPlatforms.class, WithPlatforms.class}) public abstract class AndroidBinaryTest extends AndroidBuildViewTestCase { /** Allow use of --foo as a dummy flag */ @Override protected ConfiguredRuleClassProvider createRuleClassProvider() { ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); TestRuleClassProvider.addStandardRules(builder); builder.addConfigurationFragment(DummyTestFragment.class); return builder.build(); } /** Use legacy toolchain resolution. */ @RunWith(JUnit4.class) public static class WithoutPlatforms extends AndroidBinaryTest { @Test public void testAndroidSplitTransitionWithInvalidCpu() throws Exception { scratch.file( ""test/starlark/my_rule.bzl"", ""def impl(ctx): "", "" return []"", ""my_rule = rule("", "" implementation = impl,"", "" attrs = {"", "" 'deps': attr.label_list(cfg = android_common.multi_cpu_configuration),"", "" 'dep': attr.label(cfg = android_common.multi_cpu_configuration),"", "" })""); scratch.file( ""test/starlark/BUILD"", ""load('//test/starlark:my_rule.bzl', 'my_rule')"", ""my_rule(name = 'test', deps = [':main'], dep = ':main')"", ""cc_binary(name = 'main', srcs = ['main.c'])""); BazelMockAndroidSupport.setupNdk(mockToolsConfig); // --android_cpu with --android_crosstool_top also triggers the split transition. useConfiguration( ""--fat_apk_cpu=doesnotexist"", ""--android_crosstool_top=//android/crosstool:everything""); AssertionError noToolchainError = assertThrows(AssertionError.class, () -> getConfiguredTarget(""//test/starlark:test"")); assertThat(noToolchainError) .hasMessageThat() .contains(""does not contain a toolchain for cpu 'doesnotexist'""); } } /** Use platform-based toolchain resolution. */ @RunWith(JUnit4.class) public static class WithPlatforms extends AndroidBinaryTest { @Override protected boolean platformBasedToolchains() { return true; } @Override protected String defaultPlatformFlag() { return String.format( ""--android_platforms=%sandroid:armeabi-v7a"", TestConstants.CONSTRAINTS_PACKAGE_ROOT); } @Test public void testFatApk_androidPlatformsFlag() throws Exception { BazelMockAndroidSupport.setupNdk(mockToolsConfig); scratch.file( ""java/foo/BUILD"", ""config_setting("", "" name = 'is_x86',"", "" constraint_values = ['"" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + ""cpu:x86_32'],"", "")"", ""config_setting("", "" name = 'is_arm',"", "" constraint_values = ['"" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + ""cpu:armv7'],"", "")"", ""android_library("", "" name = 'lib',"", "" srcs = ['MyLibrary.java'])"", ""cc_library("", "" name = 'arm-native-lib',"", "" srcs = ['armnative.so'])"", ""cc_library("", "" name = 'x86-native-lib',"", "" srcs = ['x86native.so'])"", ""cc_library("", "" name = 'native-lib',"", "" deps = select({"", "" ':is_x86': [':x86-native-lib'],"", "" ':is_arm': [':arm-native-lib'],"", "" '//conditions:default': [],"", "" }))"", ""android_binary("", "" name = 'abin',"", "" srcs = ['a.java'],"", "" proguard_specs = [],"", "" deps = ['lib', 'native-lib'],"", "" manifest = 'AndroidManifest.xml',"", "")""); useConfiguration( ""--android_platforms=//java/android/platforms:x86,//java/android/platforms:armeabi-v7a"", ""--dynamic_mode=off""); ConfiguredTarget apk = getConfiguredTarget(""//java/foo:abin""); List args = getGeneratingSpawnActionArgs(getCompressedUnsignedApk(apk)); assertContainsSublist( args, ImmutableList.of(""--resources"", ""java/foo/armnative.so:lib/armeabi-v7a/armnative.so"")); assertContainsSublist( args, ImmutableList.of(""--resources"", ""java/foo/x86native.so:lib/x86/x86native.so"")); } } @Before public void setupCcToolchain() throws Exception { getAnalysisMock().ccSupport().setupCcToolchainConfigForCpu(mockToolsConfig, ""armeabi-v7a""); } @Before public void setup() throws Exception { scratch.file( ""java/android/BUILD"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" )""); scratch.file( ""java/android/res/values/strings.xml"", ""Hello Android!""); scratch.file(""java/android/A.java"", ""package android; public class A {};""); if (platformBasedToolchains()) { scratch.file( ""java/android/platforms/BUILD"", ""platform("", "" name = 'x86',"", "" parents = ['"" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + ""android:armeabi-v7a'],"", "" constraint_values = ['"" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + ""cpu:x86_32'],"", "")"", ""platform("", "" name = 'armeabi-v7a',"", "" parents = ['"" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + ""android:armeabi-v7a'],"", "" constraint_values = ['"" + TestConstants.CONSTRAINTS_PACKAGE_ROOT + ""cpu:armv7'],"", "")""); scratch.file( ""/workspace/platform_mappings"", ""platforms:"", "" //java/android/platforms:armeabi-v7a"", "" --cpu=armeabi-v7a"", "" --android_cpu=armeabi-v7a"", "" --crosstool_top=//android/crosstool:everything"", "" //java/android/platforms:x86"", "" --cpu=x86"", "" --android_cpu=x86"", "" --crosstool_top=//android/crosstool:everything"", ""flags:"", "" --crosstool_top=//android/crosstool:everything"", "" --cpu=armeabi-v7a"", "" //java/android/platforms:armv7"", "" --crosstool_top=//android/crosstool:everything"", "" --cpu=x86"", "" //java/android/platforms:x86""); invalidatePackages(false); } setBuildLanguageOptions(""--experimental_google_legacy_api""); } @Test public void testAssetsInExternalRepository() throws Exception { FileSystemUtils.appendIsoLatin1( scratch.resolve(""WORKSPACE""), ""local_repository(name='r', path='/r')""); scratch.file(""/r/WORKSPACE""); scratch.file(""/r/p/BUILD"", ""filegroup(name='assets', srcs=['a/b'])""); scratch.file(""/r/p/a/b""); invalidatePackages(); scratchConfiguredTarget( ""java/a"", ""a"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" assets = ['@r//p:assets'],"", "" assets_dir = '')""); } @Test public void testMultidexModeAndMainDexProguardSpecs() throws Exception { checkError( ""java/a"", ""a"", ""only allowed if 'multidex' is set to 'legacy'"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" main_dex_proguard_specs = ['foo'])""); } @Test public void testAndroidManifestWithCustomName() throws Exception { scratchConfiguredTarget( ""java/a"", ""a"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'SomeOtherAndroidManifest.xml')""); assertNoEvents(); } @Test public void testMainDexProguardSpecs() throws Exception { useConfiguration(""--noincremental_dexing""); ConfiguredTarget ct = scratchConfiguredTarget( ""java/a"", ""a"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy',"", "" main_dex_proguard_specs = ['a.spec'])""); Artifact intermediateJar = artifactByPath( ImmutableList.of(getCompressedUnsignedApk(ct)), "".apk"", "".dex.zip"", "".dex.zip"", ""main_dex_list.txt"", ""_intermediate.jar""); List args = getGeneratingSpawnActionArgs(intermediateJar); MoreAsserts.assertContainsSublist(args, ""-include"", ""java/a/a.spec""); assertThat(Joiner.on("" "").join(args)).doesNotContain(""mainDexClasses.rules""); } @Test public void testLegacyMainDexListGenerator() throws Exception { scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy')""); scratch.file( ""tools/fake/BUILD"", ""cc_binary("", "" name = 'generate_main_dex_list',"", "" srcs = ['main.cc'])""); useConfiguration(""--legacy_main_dex_list_generator=//tools/fake:generate_main_dex_list""); ConfiguredTarget binary = getConfiguredTarget(""//java/a:a""); Artifact mainDexList = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""main_dex_list.txt""); List args = getGeneratingSpawnActionArgs(mainDexList); NestedSet mainDexInputs = getGeneratingAction(mainDexList).getInputs(); MoreAsserts.assertContainsSublist(args, ""--lib"", getAndroidJarPath()); MoreAsserts.assertContainsSublist(args, ""--main-dex-rules"", getMainDexClassesPath()); assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains(""generate_main_dex_list""); assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains(""a_deploy.jar""); assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains(getAndroidJarFilename()); assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)) .contains(getMainDexClassesFilename()); assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)) .contains(""main_dex_a_proguard.cfg""); assertThat(getFirstArtifactEndingWith(mainDexInputs, ""main_dex_list_creator"")).isNull(); } @Test public void testOptimizedDexingWithLegacyMultidex() throws Exception { scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['specs.pgcfg'],"", "" proguard_generate_mapping = True,"", "" multidex = 'legacy')""); scratch.file( ""tools/fake/BUILD"", ""cc_binary("", "" name = 'optimizing_dexer',"", "" srcs = ['main.cc'])""); useConfiguration(""--optimizing_dexer=//tools/fake:optimizing_dexer""); ConfiguredTarget binary = getConfiguredTarget(""//java/a:a""); Artifact proguardedJar = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""a_proguard.jar""); Artifact proguardMap = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""a_proguard.map""); Artifact dexedZip = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/_dx/a/classes.dex.zip""); SpawnAction proguard = getGeneratingSpawnAction(proguardedJar); SpawnAction dexer = getGeneratingSpawnAction(dexedZip); assertThat(dexer).isEqualTo(getGeneratingSpawnAction(proguardMap)); List dexerArgs = dexer.getArguments(); assertThat(dexerArgs).contains(""--release""); assertThat(dexerArgs).contains(""--no-desugaring""); MoreAsserts.assertContainsSublist(dexerArgs, ""--lib"", getAndroidJarPath()); Artifact mainDexList = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/_dx/a/main_dex_list.txt""); MoreAsserts.assertContainsSublist( dexerArgs, ""--main-dex-list"", mainDexList.getExecPath().getPathString()); Artifact proguardMapInput = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/proguard/a/legacy_a_pre_dexing.map""); MoreAsserts.assertContainsSublist( dexerArgs, ""--pg-map"", proguardMapInput.getExecPath().getPathString()); } @Test public void testOptimizedDexingWithNativeMultidex() throws Exception { scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['specs.pgcfg'],"", "" proguard_generate_mapping = True,"", "" multidex = 'native')""); scratch.file( ""tools/fake/BUILD"", ""cc_binary("", "" name = 'optimizing_dexer',"", "" srcs = ['main.cc'])""); useConfiguration(""--optimizing_dexer=//tools/fake:optimizing_dexer""); // The behavior should match legacy multidex except for the main dex list and min sdk, so we // only check those flags. ConfiguredTarget binary = getConfiguredTarget(""//java/a:a""); Artifact dexedZip = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/_dx/a/classes.dex.zip""); SpawnAction dexer = getGeneratingSpawnAction(dexedZip); List dexerArgs = dexer.getArguments(); assertThat(dexerArgs).doesNotContain(""--main-dex-list""); MoreAsserts.assertContainsSublist(dexerArgs, ""--min-api"", ""21""); } @Test public void testMainDexListObfuscation() throws Exception { useConfiguration(""--noincremental_dexing""); scratch.file(""/java/a/list.txt""); ConfiguredTarget ct = scratchConfiguredTarget( ""java/a"", ""a"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'manual_main_dex',"", "" proguard_generate_mapping = 1,"", "" main_dex_list = 'list.txt')""); Artifact obfuscatedDexList = artifactByPath( ImmutableList.of(getCompressedUnsignedApk(ct)), "".apk"", "".dex.zip"", "".dex.zip"", ""main_dex_list_obfuscated.txt""); List args = getGeneratingSpawnActionArgs(obfuscatedDexList); assertThat(args.get(0)).contains(""dex_list_obfuscator""); MoreAsserts.assertContainsSublist(args, ""--input"", ""java/a/list.txt""); } @Test public void testNonLegacyNativeDepsDoesNotPolluteDexSharding() throws Exception { scratch.file( ""java/a/BUILD"", ""android_binary(name = 'a',"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "" deps = [':cc'],"", "" dex_shards = 2)"", ""cc_library(name = 'cc',"", "" srcs = ['cc.cc'])""); Artifact jarShard = artifactByPath( ImmutableList.of(getCompressedUnsignedApk(getConfiguredTarget(""//java/a:a""))), "".apk"", ""classes.dex.zip"", ""shard1.dex.zip"", ""shard1.jar.dex.zip""); NestedSet shardInputs = getGeneratingAction(jarShard).getInputs(); assertThat(getFirstArtifactEndingWith(shardInputs, "".txt"")).isNull(); } @Test public void testCcInfoDeps() throws Exception { scratch.file( ""java/a/cc_info.bzl"", ""def _impl(ctx):"", "" cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]"", "" feature_configuration = cc_common.configure_features("", "" ctx = ctx,"", "" cc_toolchain = cc_toolchain,"", "" requested_features = ctx.features,"", "" unsupported_features = ctx.disabled_features,"", "" )"", "" library_to_link = cc_common.create_library_to_link("", "" actions=ctx.actions, feature_configuration=feature_configuration, "", "" cc_toolchain = cc_toolchain, "", "" static_library=ctx.file.static_library)"", "" linker_input = cc_common.create_linker_input("", "" libraries = depset([library_to_link]),"", "" user_link_flags=depset(ctx.attr.user_link_flags),"", "" owner = ctx.label,"", "" )"", "" linking_context = cc_common.create_linking_context("", "" linker_inputs=depset([linker_input]))"", "" return [CcInfo(linking_context=linking_context)]"", ""cc_info = rule("", "" implementation=_impl,"", "" fragments = [\""cpp\""],"", "" attrs = {"", "" 'srcs': attr.label_list(allow_files=True),"", "" 'user_link_flags' : attr.string_list(),"", "" 'static_library': attr.label(allow_single_file=True),"", "" '_cc_toolchain': attr.label(default=Label('//java/a:alias'))"", "" },"", "");""); scratch.file( ""java/a/BUILD"", ""load('//java/a:cc_info.bzl', 'cc_info')"", ""cc_toolchain_alias(name='alias')"", ""android_binary("", "" name = 'a',"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "" deps = [':cc_info'],"", "")"", ""cc_info("", "" name = 'cc_info',"", "" user_link_flags = ['-first_flag', '-second_flag'],"", "" static_library = 'cc_info.a',"", "")""); ConfiguredTarget app = getConfiguredTarget(""//java/a:a""); assertNoEvents(); Artifact copiedLib = getOnlyElement(getNativeLibrariesInApk(app)); Artifact linkedLib = getGeneratingAction(copiedLib).getInputs().getSingleton(); CppLinkAction action = (CppLinkAction) getGeneratingAction(linkedLib); assertThat(action.getArguments()).containsAtLeast(""-first_flag"", ""-second_flag""); NestedSet linkInputs = action.getInputs(); assertThat(ActionsTestUtil.baseArtifactNames(linkInputs)).contains(""cc_info.a""); } @Test public void testCcInfoDepsViaAndroidLibrary() throws Exception { scratch.file( ""java/a/cc_info.bzl"", ""def _impl(ctx):"", "" cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]"", "" feature_configuration = cc_common.configure_features("", "" ctx = ctx,"", "" cc_toolchain = cc_toolchain,"", "" requested_features = ctx.features,"", "" unsupported_features = ctx.disabled_features,"", "" )"", "" library_to_link = cc_common.create_library_to_link("", "" actions=ctx.actions, feature_configuration=feature_configuration, "", "" cc_toolchain = cc_toolchain, "", "" static_library=ctx.file.static_library)"", "" linker_input = cc_common.create_linker_input("", "" libraries = depset([library_to_link]),"", "" user_link_flags=depset(ctx.attr.user_link_flags),"", "" owner = ctx.label,"", "" )"", "" linking_context = cc_common.create_linking_context("", "" linker_inputs=depset([linker_input]))"", "" return [CcInfo(linking_context=linking_context)]"", ""cc_info = rule("", "" implementation=_impl,"", "" fragments = [\""cpp\""],"", "" attrs = {"", "" 'srcs': attr.label_list(allow_files=True),"", "" 'user_link_flags' : attr.string_list(),"", "" 'static_library': attr.label(allow_single_file=True),"", "" '_cc_toolchain': attr.label(default=Label('//java/a:alias'))"", "" },"", "");""); scratch.file( ""java/a/BUILD"", ""load('//java/a:cc_info.bzl', 'cc_info')"", ""cc_toolchain_alias(name='alias')"", ""android_binary("", "" name = 'a',"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "" deps = [':liba'],"", "")"", ""android_library("", "" name = 'liba',"", "" srcs = ['a.java'],"", "" deps = [':cc_info'],"", "")"", ""cc_info("", "" name = 'cc_info',"", "" user_link_flags = ['-first_flag', '-second_flag'],"", "" static_library = 'cc_info.a',"", "")""); ConfiguredTarget app = getConfiguredTarget(""//java/a:a""); Artifact copiedLib = getOnlyElement(getNativeLibrariesInApk(app)); Artifact linkedLib = getGeneratingAction(copiedLib).getInputs().getSingleton(); CppLinkAction action = (CppLinkAction) getGeneratingAction(linkedLib); assertThat(action.getArguments()).containsAtLeast(""-first_flag"", ""-second_flag""); NestedSet linkInputs = action.getInputs(); assertThat(ActionsTestUtil.baseArtifactNames(linkInputs)).contains(""cc_info.a""); } @Test public void testJavaPluginProcessorPath() throws Exception { scratch.file( ""java/test/BUILD"", ""java_library(name = 'plugin_dep',"", "" srcs = [ 'ProcessorDep.java'])"", ""java_plugin(name = 'plugin',"", "" srcs = ['AnnotationProcessor.java'],"", "" processor_class = 'com.google.process.stuff',"", "" deps = [ ':plugin_dep' ])"", ""android_binary(name = 'to_be_processed',"", "" manifest = 'AndroidManifest.xml',"", "" plugins = [':plugin'],"", "" srcs = ['ToBeProcessed.java'])""); ConfiguredTarget target = getConfiguredTarget(""//java/test:to_be_processed""); JavaCompileAction javacAction = (JavaCompileAction) getGeneratingAction(getBinArtifact(""libto_be_processed.jar"", target)); assertThat(getProcessorNames(javacAction)).contains(""com.google.process.stuff""); assertThat(getProcessorNames(javacAction)).hasSize(1); assertThat( ActionsTestUtil.baseArtifactNames( getInputs(javacAction, getProcessorpath(javacAction)))) .containsExactly(""libplugin.jar"", ""libplugin_dep.jar""); assertThat( actionsTestUtil() .predecessorClosureOf(getFilesToBuild(target), JavaSemantics.JAVA_SOURCE)) .isEqualTo(""ToBeProcessed.java AnnotationProcessor.java ProcessorDep.java""); } // Same test as above, enabling the plugin through the command line. @Test public void testPluginCommandLine() throws Exception { scratch.file( ""java/test/BUILD"", ""java_library(name = 'plugin_dep',"", "" srcs = [ 'ProcessorDep.java'])"", ""java_plugin(name = 'plugin',"", "" srcs = ['AnnotationProcessor.java'],"", "" processor_class = 'com.google.process.stuff',"", "" deps = [ ':plugin_dep' ])"", ""android_binary(name = 'to_be_processed',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = ['ToBeProcessed.java'])""); useConfiguration(""--plugin=//java/test:plugin""); ConfiguredTarget target = getConfiguredTarget(""//java/test:to_be_processed""); JavaCompileAction javacAction = (JavaCompileAction) getGeneratingAction(getBinArtifact(""libto_be_processed.jar"", target)); assertThat(getProcessorNames(javacAction)).contains(""com.google.process.stuff""); assertThat(getProcessorNames(javacAction)).hasSize(1); assertThat( ActionsTestUtil.baseArtifactNames( getInputs(javacAction, getProcessorpath(javacAction)))) .containsExactly(""libplugin.jar"", ""libplugin_dep.jar""); assertThat( actionsTestUtil() .predecessorClosureOf(getFilesToBuild(target), JavaSemantics.JAVA_SOURCE)) .isEqualTo(""ToBeProcessed.java AnnotationProcessor.java ProcessorDep.java""); } @Test public void testInvalidPlugin() throws Exception { checkError( ""java/test"", ""lib"", // error: getErrorMsgMandatoryProviderMissing(""//java/test:not_a_plugin"", ""JavaPluginInfo""), // BUILD file: ""java_library(name = 'not_a_plugin',"", "" srcs = [ 'NotAPlugin.java'])"", ""android_binary(name = 'lib',"", "" plugins = [':not_a_plugin'],"", "" manifest = 'AndroidManifest.xml',"", "" srcs = ['Lib.java'])""); } @Test public void testBaselineCoverageArtifacts() throws Exception { useConfiguration(""--collect_code_coverage""); ConfiguredTarget target = scratchConfiguredTarget( ""java/com/google/a"", ""bin"", ""android_binary(name='bin', srcs=['Main.java'], manifest='AndroidManifest.xml')""); assertThat(baselineCoverageArtifactBasenames(target)).containsExactly(""Main.java""); } @Test public void testSameSoFromMultipleDeps() throws Exception { scratch.file( ""java/d/BUILD"", ""genrule(name='genrule', srcs=[], outs=['genrule.so'], cmd='')"", ""cc_library(name='cc1', srcs=[':genrule.so'])"", ""cc_library(name='cc2', srcs=[':genrule.so'])"", ""android_binary(name='ab', deps=[':cc1', ':cc2'], manifest='AndroidManifest.xml')""); getConfiguredTarget(""//java/d:ab""); } @Test public void testSimpleBinary_desugarJava8() throws Exception { useConfiguration(""--experimental_desugar_for_android""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""_deploy.jar""); assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())) .contains(""libapp.jar_desugared.jar""); assertThat(ActionsTestUtil.baseArtifactNames(action.getInputs())).doesNotContain(""libapp.jar""); } /** * Tests that --experimental_check_desugar_deps causes the relevant flags to be set on desugaring * and singlejar actions, and makes sure the deploy jar is built even when just building an APK. */ @Test public void testSimpleBinary_checkDesugarDepsAlwaysHappens() throws Exception { useConfiguration(""--experimental_check_desugar_deps""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); assertNoEvents(); // 1. Find app's deploy jar and make sure checking flags are set for it and its inputs SpawnAction singlejar = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith(getFilesToBuild(binary), ""/app_deploy.jar""); assertThat(getGeneratingSpawnActionArgs(singlejar.getPrimaryOutput())) .contains(""--check_desugar_deps""); SpawnAction desugar = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith(singlejar.getInputs(), ""/libapp.jar_desugared.jar""); assertThat(desugar).isNotNull(); assertThat(getGeneratingSpawnActionArgs(desugar.getPrimaryOutput())) .contains(""--emit_dependency_metadata_as_needed""); // 2. Make sure all APK outputs depend on the deploy Jar. int found = 0; for (Artifact built : getFilesToBuild(binary).toList()) { if (built.getExtension().equals(""apk"")) { // If this assertion breaks then APK artifacts have stopped depending on deploy jars. // If that's desired then we'll need to make sure dependency checking is done in another // action that APK artifacts depend on, in addition to the check that happens when building // deploy.jars, which we assert above. assertWithMessage(""%s dependency on deploy.jar"", built.getFilename()) .that(actionsTestUtil().artifactClosureOf(built)) .contains(singlejar.getPrimaryOutput()); ++found; } } assertThat(found).isEqualTo(2 /* signed and unsigned apks */); } @Test public void testSimpleBinary_dexNoMinSdkVersion() throws Exception { scratch.overwriteFile( ""java/android/BUILD"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" multidex = 'legacy',"", "" )""); useConfiguration(""--experimental_desugar_java8_libs""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); List args = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/libapp.jar.dex.zip"")); assertThat(args).doesNotContain(""--min_sdk_version""); } @Test public void testSimpleBinary_dexMinSdkVersion() throws Exception { scratch.overwriteFile( ""java/android/BUILD"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" min_sdk_version = 28,"", "" multidex = 'legacy',"", "" )""); useConfiguration(""--experimental_desugar_java8_libs""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); List args = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/libapp.jar.dex.zip"")); assertThat(args).contains(""--min_sdk_version""); assertThat(args).contains(""28""); } @Test public void testSimpleBinary_desugarNoMinSdkVersion() throws Exception { scratch.overwriteFile( ""java/android/BUILD"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" multidex = 'legacy',"", "" )""); useConfiguration(""--experimental_desugar_java8_libs""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); List args = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/libapp.jar_desugared.jar"")); assertThat(args).doesNotContain(""--min_sdk_version""); } @Test public void testSimpleBinary_desugarMinSdkVersion() throws Exception { scratch.overwriteFile( ""java/android/BUILD"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" min_sdk_version = 28,"", "" multidex = 'legacy',"", "" )""); useConfiguration(""--experimental_desugar_java8_libs""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); List args = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""/libapp.jar_desugared.jar"")); assertThat(args).contains(""--min_sdk_version""); assertThat(args).contains(""28""); } @Test public void testSimpleAndroidBinary_multipleMinSdkVersionOnSameLibrary() throws Exception { scratch.overwriteFile( ""java/android/BUILD"", ""android_binary(name = 'app1',"", "" srcs = ['A.java'],"", "" deps = ['lib'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" )"", ""android_binary(name = 'app2',"", "" srcs = ['B.java'],"", "" deps = ['lib'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" min_sdk_version = 28,"", "" )"", ""android_library(name = 'lib',"", "" srcs = ['C.java'])""); AnalysisResult result = update( ImmutableList.of(""//java/android:app1"", ""//java/android:app2""), /* keepGoing= */ true, /* loadingPhaseThreads= */ 1, /* doAnalysis= */ true, eventBus); ImmutableMap targets = result.getTargetsToBuild().stream() .collect(toImmutableMap(ct -> ct.getLabel().toString(), Function.identity())); ConfiguredTarget app1 = targets.get(""//java/android:app1""); ConfiguredTarget app2 = targets.get(""//java/android:app2""); // The ""lib"" target is built twice: once with the default minsdk from app1, and again with // min_sdk_version = 28 from app2. List libDesugarNoMinSdkArgs = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(app1)), ""/liblib.jar_desugared.jar"")); assertThat(libDesugarNoMinSdkArgs).doesNotContain(""--min_sdk_version""); List libDexNoMinSdkArgs = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(app1)), ""/liblib.jar.dex.zip"")); assertThat(libDexNoMinSdkArgs).doesNotContain(""--min_sdk_version""); List libDesugarWithMinSdkArgs = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(app2)), ""/liblib.jar_minsdk=28_desugared.jar"")); assertThat(libDesugarWithMinSdkArgs).containsAtLeast(""--min_sdk_version"", ""28"").inOrder(); List libDexWithMinSdkArgs = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(app2)), ""liblib.jar--min_sdk_version=28.dex.zip"")); assertThat(libDexWithMinSdkArgs).containsAtLeast(""--min_sdk_version"", ""28"").inOrder(); } // regression test for #3169099 @Test public void testBinarySrcs() throws Exception { scratch.file(""java/srcs/a.foo"", ""foo""); scratch.file( ""java/srcs/BUILD"", ""android_binary(name = 'valid', manifest = 'AndroidManifest.xml', "" + ""srcs = ['a.java', 'b.srcjar', ':gvalid', ':gmix'])"", ""android_binary(name = 'invalid', manifest = 'AndroidManifest.xml', "" + ""srcs = ['a.foo', ':ginvalid'])"", ""android_binary(name = 'mix', manifest = 'AndroidManifest.xml', "" + ""srcs = ['a.java', 'a.foo'])"", ""genrule(name = 'gvalid', srcs = ['a.java'], outs = ['b.java'], cmd = '')"", ""genrule(name = 'ginvalid', srcs = ['a.java'], outs = ['b.foo'], cmd = '')"", ""genrule(name = 'gmix', srcs = ['a.java'], outs = ['c.java', 'c.foo'], cmd = '')""); assertSrcsValidityForRuleType(""//java/srcs"", ""android_binary"", "".java or .srcjar""); } // regression test for #3169095 @Test public void testXmbInSrcs_notPermittedButDoesNotThrow() throws Exception { reporter.removeHandler(failFastHandler); scratchConfiguredTarget( ""java/xmb"", ""a"", ""android_binary(name = 'a', manifest = 'AndroidManifest.xml', srcs = ['a.xmb'])""); // We expect there to be an error here because a.xmb is not a valid src, // and more importantly, no exception to have been thrown. assertContainsEvent( ""in srcs attribute of android_binary rule //java/xmb:a: "" + ""target '//java/xmb:a.xmb' does not exist""); } @Test public void testNativeLibraryBasenameCollision() throws Exception { reporter.removeHandler(failFastHandler); // expect errors scratch.file( ""java/android/common/BUILD"", ""cc_library(name = 'libcommon_armeabi',"", "" srcs = ['armeabi/native.so'],)""); scratch.file( ""java/android/app/BUILD"", ""cc_library(name = 'libnative',"", "" srcs = ['native.so'],)"", ""android_binary(name = 'b',"", "" srcs = ['A.java'],"", "" deps = [':libnative', '//java/android/common:libcommon_armeabi'],"", "" manifest = 'AndroidManifest.xml',"", "" )""); getConfiguredTarget(""//java/android/app:b""); assertContainsEvent( ""Each library in the transitive closure must have a unique basename to avoid name"" + "" collisions when packaged into an apk, but two libraries have the basename"" + "" 'native.so': java/android/common/armeabi/native.so and"" + "" java/android/app/native.so""); } private void setupNativeLibrariesForLinking() throws Exception { scratch.file( ""java/android/common/BUILD"", ""cc_library(name = 'common_native',"", "" srcs = ['common.cc'],)"", ""android_library(name = 'common',"", "" exports = [':common_native'],)""); scratch.file( ""java/android/app/BUILD"", ""cc_library(name = 'native',"", "" srcs = ['native.cc'],)"", ""android_binary(name = 'auto',"", "" srcs = ['A.java'],"", "" deps = [':native', '//java/android/common:common'],"", "" manifest = 'AndroidManifest.xml',"", "" )"", ""android_binary(name = 'off',"", "" srcs = ['A.java'],"", "" deps = [':native', '//java/android/common:common'],"", "" manifest = 'AndroidManifest.xml',"", "" )""); } private void assertNativeLibraryLinked(ConfiguredTarget target, String... srcNames) { Artifact linkedLib = getOnlyElement(getNativeLibrariesInApk(target)); assertThat(linkedLib.getFilename()) .isEqualTo(""lib"" + target.getLabel().toPathFragment().getBaseName() + "".so""); assertThat(linkedLib.isSourceArtifact()).isFalse(); assertWithMessage(""Native libraries were not linked to produce "" + linkedLib) .that(getGeneratingLabelForArtifact(linkedLib)) .isEqualTo(target.getLabel()); assertThat(artifactsToStrings(actionsTestUtil().artifactClosureOf(linkedLib))) .containsAtLeastElementsIn(ImmutableSet.copyOf(Arrays.asList(srcNames))); } @Test public void testNativeLibrary_linksLibrariesWhenCodeIsPresent() throws Exception { setupNativeLibrariesForLinking(); assertNativeLibraryLinked( getConfiguredTarget(""//java/android/app:auto""), ""src java/android/common/common.cc"", ""src java/android/app/native.cc""); assertNativeLibraryLinked( getConfiguredTarget(""//java/android/app:off""), ""src java/android/common/common.cc"", ""src java/android/app/native.cc""); } @Test public void testNativeLibrary_copiesLibrariesDespiteExtraLayersOfIndirection() throws Exception { setBuildLanguageOptions( ""--experimental_builtins_injection_override=+cc_library"", ""--experimental_google_legacy_api""); scratch.file( ""java/android/app/BUILD"", ""cc_library(name = 'native_dep',"", "" srcs = ['dep.so'])"", ""cc_library(name = 'native',"", "" srcs = ['native_prebuilt.so'],"", "" deps = [':native_dep'])"", ""cc_library(name = 'native_wrapper',"", "" deps = [':native'])"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" deps = [':native_wrapper'],"", "" manifest = 'AndroidManifest.xml',"", "" )""); ConfiguredTarget app = getConfiguredTarget(""//java/android/app:app""); assertNativeLibrariesCopiedNotLinked( app, getConfiguration(getDirectPrerequisite(app, ""//java/android/app:native_wrapper"")), ""src java/android/app/dep.so"", ""src java/android/app/native_prebuilt.so""); } @Test public void testNativeLibrary_copiesLibrariesWrappedInCcLibraryWithSameName() throws Exception { scratch.file( ""java/android/app/BUILD"", ""cc_library(name = 'native',"", "" srcs = ['libnative.so'])"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" deps = [':native'],"", "" manifest = 'AndroidManifest.xml',"", "" )""); ConfiguredTarget app = getConfiguredTarget(""//java/android/app:app""); assertNativeLibrariesCopiedNotLinked( app, getConfiguration(getDirectPrerequisite(app, ""//java/android/app:native"")), ""src java/android/app/libnative.so""); } @Test public void testNativeLibrary_linksWhenPrebuiltArchiveIsSupplied() throws Exception { scratch.file( ""java/android/app/BUILD"", ""cc_library(name = 'native_dep',"", "" srcs = ['dep.lo'])"", ""cc_library(name = 'native',"", "" srcs = ['native_prebuilt.a'],"", "" deps = [':native_dep'])"", ""cc_library(name = 'native_wrapper',"", "" deps = [':native'])"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" deps = [':native_wrapper'],"", "" manifest = 'AndroidManifest.xml',"", "" )""); assertNativeLibraryLinked( getConfiguredTarget(""//java/android/app:app""), ""src java/android/app/native_prebuilt.a""); } @Test public void testNativeLibrary_copiesFullLibrariesInIfsoMode() throws Exception { useConfiguration(""--interface_shared_objects""); scratch.file( ""java/android/app/BUILD"", ""cc_library(name = 'native_dep',"", "" srcs = ['dep.so'])"", ""cc_library(name = 'native',"", "" srcs = ['native.cc', 'native_prebuilt.so'],"", "" deps = [':native_dep'])"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" deps = [':native'],"", "" manifest = 'AndroidManifest.xml',"", "" )""); ConfiguredTarget app = getConfiguredTarget(""//java/android/app:app""); Iterable nativeLibraries = getNativeLibrariesInApk(app); assertThat(artifactsToStrings(nativeLibraries)) .containsAtLeast(""src java/android/app/native_prebuilt.so"", ""src java/android/app/dep.so""); assertThat(FileType.filter(nativeLibraries, CppFileTypes.INTERFACE_SHARED_LIBRARY)).isEmpty(); } @Test public void testNativeLibrary_providesLinkerScriptToLinkAction() throws Exception { scratch.file( ""java/android/app/BUILD"", ""cc_library(name = 'native',"", "" srcs = ['native.cc'],"", "" linkopts = ['-Wl,-version-script', '$(location jni.lds)'],"", "" deps = ['jni.lds'],)"", ""android_binary(name = 'app',"", "" srcs = ['A.java'],"", "" deps = [':native'],"", "" manifest = 'AndroidManifest.xml',"", "" )""); ConfiguredTarget app = getConfiguredTarget(""//java/android/app:app""); Artifact copiedLib = getOnlyElement(getNativeLibrariesInApk(app)); Artifact linkedLib = getGeneratingAction(copiedLib).getInputs().getSingleton(); NestedSet linkInputs = getGeneratingAction(linkedLib).getInputs(); assertThat(ActionsTestUtil.baseArtifactNames(linkInputs)).contains(""jni.lds""); } /** Regression test for http://b/33173461. */ @Test public void testIncrementalDexingUsesDexArchives_binaryDependingOnAliasTarget() throws Exception { useConfiguration( ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""java/com/google/android/BUILD"", ""android_library("", "" name = 'dep',"", "" srcs = ['dep.java'],"", "" manifest = 'AndroidManifest.xml',"", "")"", ""alias("", "" name = 'alt',"", "" actual = ':dep',"", "")"", ""android_binary("", "" name = 'top',"", "" srcs = ['foo.java', 'bar.srcjar'],"", "" multidex = 'native',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':alt',],"", "")""); ConfiguredTarget topTarget = getConfiguredTarget(""//java/com/google/android:top""); assertNoEvents(); Action shardAction = getGeneratingAction(getBinArtifact(""_dx/top/classes.jar"", topTarget)); for (Artifact input : getNonToolInputs(shardAction)) { String basename = input.getFilename(); // all jars are converted to dex archives assertWithMessage(basename) .that(!basename.contains("".jar"") || basename.endsWith("".jar.dex.zip"")) .isTrue(); // all jars are desugared before being converted if (basename.endsWith("".jar.dex.zip"")) { assertThat(getGeneratingAction(input).getPrimaryInput().getFilename()) .isEqualTo( basename.substring(0, basename.length() - "".jar.dex.zip"".length()) + "".jar_desugared.jar""); } } // Make sure exactly the dex archives generated for top and dependents appear. We also *don't* // want neverlink and unused_dep to appear, and to be safe we do so by explicitly enumerating // *all* expected input dex archives. assertThat( Iterables.filter( ActionsTestUtil.baseArtifactNames(getNonToolInputs(shardAction)), Predicates.containsPattern(""\\.jar""))) .containsExactly( // top's dex archives ""libtop.jar.dex.zip"", ""top_resources.jar.dex.zip"", // dep's dex archives ""libdep.jar.dex.zip""); } @Test public void testIncrementalDexingDisabledWithBlacklistedDexopts() throws Exception { // Even if we mark a dx flag as supported, incremental dexing isn't used with disallowlisted // dexopts (unless incremental_dexing attribute is set, which a different test covers) useConfiguration( ""--incremental_dexing"", ""--non_incremental_per_target_dexopts=--no-locals"", ""--dexopts_supported_in_incremental_dexing=--no-locals""); scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'top',"", "" srcs = ['foo.java', 'bar.srcjar'],"", "" manifest = 'AndroidManifest.xml',"", "" dexopts = ['--no-locals'],"", "" dex_shards = 2,"", "" multidex = 'native',"", "")""); ConfiguredTarget topTarget = getConfiguredTarget(""//java/com/google/android:top""); assertNoEvents(); Action shardAction = getGeneratingAction(getBinArtifact(""_dx/top/shard1.jar"", topTarget)); assertThat( Iterables.filter( ActionsTestUtil.baseArtifactNames(getNonToolInputs(shardAction)), Predicates.containsPattern(""\\.jar\\.dex\\.zip""))) .isEmpty(); // no dex archives are used } @Test public void testIncrementalDexingDisabledWithProguard() throws Exception { useConfiguration( ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'top',"", "" srcs = ['foo.java', 'bar.srcjar'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard.cfg'],"", "")""); ConfiguredTarget topTarget = getConfiguredTarget(""//java/com/google/android:top""); assertNoEvents(); Action dexAction = getGeneratingAction(getBinArtifact(""_dx/top/intermediate_classes.dex.zip"", topTarget)); assertThat( Iterables.filter( ActionsTestUtil.baseArtifactNames(dexAction.getInputs()), Predicates.containsPattern(""\\.jar""))) .containsExactly(""top_proguard.jar"", ""dx_binary.jar""); // proguard output is used directly } @Test public void testIncrementalDexing_incompatibleWithProguardWhenDisabled() throws Exception { useConfiguration( ""--experimental_incremental_dexing_after_proguard=0"", // disable with Proguard ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false""); checkError( ""java/com/google/android"", ""top"", ""target cannot be incrementally dexed"", ""android_binary("", "" name = 'top',"", "" srcs = ['foo.java', 'bar.srcjar'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard.cfg'],"", "" incremental_dexing = 1,"", "")""); } @Test public void testIncrementalDexingAfterProguard_unsharded() throws Exception { useConfiguration(""--experimental_incremental_dexing_after_proguard=1""); // Use ""legacy"" multidex mode so we get a main dex list file and can test that it's passed to // the splitter action (similar to _withDexShards below), unlike without the dex splitter where // the main dex list goes to the merging action. scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'top',"", "" srcs = ['foo.java', 'bar.srcjar'],"", "" manifest = 'AndroidManifest.xml',"", "" incremental_dexing = 1,"", "" multidex = 'legacy',"", "" dexopts = ['--minimal-main-dex', '--positions=none'],"", "" proguard_specs = ['b.pro'],"", "")""); ConfiguredTarget topTarget = getConfiguredTarget(""//java/com/google/android:top""); assertNoEvents(); SpawnAction shardAction = getGeneratingSpawnAction(getBinArtifact(""_dx/top/classes.dex.zip"", topTarget)); assertThat(shardAction.getArguments()).contains(""--main-dex-list""); assertThat(shardAction.getArguments()).contains(""--minimal-main-dex""); assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(shardAction))) .containsExactly(""classes.jar"", ""main_dex_list.txt""); // --positions dexopt is supported after Proguard, even though not normally otherwise assertThat( paramFileArgsForAction( getGeneratingSpawnAction(getBinArtifact(""_dx/top/classes.jar"", topTarget)))) .contains(""--positions=none""); } @Test public void testIncrementalDexingAfterProguard_autoShardedMultidexAutoOptIn() throws Exception { useConfiguration( ""--experimental_incremental_dexing_after_proguard=3"", ""--experimental_incremental_dexing_after_proguard_by_default""); // Use ""legacy"" multidex mode so we get a main dex list file and can test that it's passed to // the splitter action (similar to _withDexShards below), unlike without the dex splitter where // the main dex list goes to the merging action. scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'top',"", "" srcs = ['foo.java', 'bar.srcjar'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy',"", "" dexopts = ['--minimal-main-dex', '--positions=none'],"", "" proguard_specs = ['b.pro'],"", "")""); // incremental_dexing = 1 attribute not needed ConfiguredTarget topTarget = getConfiguredTarget(""//java/com/google/android:top""); assertNoEvents(); SpawnAction splitAction = getGeneratingSpawnAction(getTreeArtifact(""dexsplits/top"", topTarget)); assertThat(splitAction.getArguments()).contains(""--main-dex-list""); assertThat(splitAction.getArguments()).contains(""--minimal-main-dex""); assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(splitAction))) .containsExactly( ""shard1.jar.dex.zip"", ""shard2.jar.dex.zip"", ""shard3.jar.dex.zip"", ""main_dex_list.txt""); SpawnAction shuffleAction = getGeneratingSpawnAction(getBinArtifact(""_dx/top/shard1.jar"", topTarget)); assertThat(shuffleAction.getArguments()).doesNotContain(""--main-dex-list""); assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(shuffleAction))) .containsExactly(""top_proguard.jar""); // --positions dexopt is supported after Proguard, even though not normally otherwise assertThat( paramFileArgsForAction( getGeneratingSpawnAction(getBinArtifact(""_dx/top/shard3.jar.dex.zip"", topTarget)))) .contains(""--positions=none""); } @Test public void testIncrementalDexingAfterProguard_explicitDexShards() throws Exception { useConfiguration(""--experimental_incremental_dexing_after_proguard=2""); // Use ""legacy"" multidex mode so we get a main dex list file and can test that it's passed to // the shardAction, not to the subsequent dexMerger action. Without dex_shards, main dex list // file goes to the dexMerger instead (see _multidex test). scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'top',"", "" srcs = ['foo.java', 'bar.srcjar'],"", "" manifest = 'AndroidManifest.xml',"", "" dex_shards = 25,"", "" incremental_dexing = 1,"", "" multidex = 'legacy',"", "" proguard_specs = ['b.pro'],"", "")""); ConfiguredTarget topTarget = getConfiguredTarget(""//java/com/google/android:top""); assertNoEvents(); SpawnAction shardAction = getGeneratingSpawnAction(getBinArtifact(""_dx/top/shard25.jar"", topTarget)); assertThat(shardAction.getArguments()).contains(""--main_dex_filter""); assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(shardAction))) .containsExactly(""top_proguard.jar"", ""main_dex_list.txt""); SpawnAction mergeAction = getGeneratingSpawnAction(getBinArtifact(""_dx/top/shard1.jar.dex.zip"", topTarget)); assertThat(mergeAction.getArguments()).doesNotContain(""--main-dex-list""); assertThat(ActionsTestUtil.baseArtifactNames(getNonToolInputs(mergeAction))) .contains(""shard1.jar""); } @Test public void testV1SigningMethod() throws Exception { actualSignerToolTests(""v1"", ""true"", ""false"", null); } @Test public void testV2SigningMethod() throws Exception { actualSignerToolTests(""v2"", ""false"", ""true"", null); } @Test public void testV1V2SigningMethod() throws Exception { actualSignerToolTests(""v1_v2"", ""true"", ""true"", null); } @Test public void testV4SigningMethod() throws Exception { actualSignerToolTests(""v4"", ""false"", ""false"", ""true""); } private void actualSignerToolTests( String apkSigningMethod, String signV1, String signV2, String signV4) throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',)""); useConfiguration(""--apk_signing_method="" + apkSigningMethod); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:hello""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); assertThat(getFirstArtifactEndingWith(artifacts, ""signed_hello.apk"")).isNull(); SpawnAction unsignedApkAction = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(artifacts, ""/hello_unsigned.apk""); assertThat( unsignedApkAction.getInputs().toList().stream() .map(Artifact::getFilename) .anyMatch(filename -> Ascii.toLowerCase(filename).contains(""singlejar""))) .isTrue(); SpawnAction compressedUnsignedApkAction = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith(artifacts, ""compressed_hello_unsigned.apk""); assertThat( compressedUnsignedApkAction.getInputs().toList().stream() .map(Artifact::getFilename) .anyMatch(filename -> Ascii.toLowerCase(filename).contains(""singlejar""))) .isTrue(); SpawnAction zipalignAction = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(artifacts, ""zipaligned_hello.apk""); assertThat(zipalignAction.getCommandFilename()).endsWith(""zipalign""); Artifact a = ActionsTestUtil.getFirstArtifactEndingWith(artifacts, ""hello.apk""); assertThat(getGeneratingSpawnAction(a).getCommandFilename()).endsWith(""ApkSignerBinary""); List args = getGeneratingSpawnActionArgs(a); assertThat(flagValue(""--v1-signing-enabled"", args)).isEqualTo(signV1); assertThat(flagValue(""--v2-signing-enabled"", args)).isEqualTo(signV2); if (signV4 != null) { assertThat(flagValue(""--v4-signing-enabled"", args)).isEqualTo(signV4); if (signV4.equals(""true"")) { assertThat(getFirstArtifactEndingWith(artifacts, ""hello.apk.idsig"")).isNotNull(); } } else { assertThat(args).doesNotContain(""--v4-signing-enabled""); } } @Test public void testResourcePathShortening_flagEnabledAndCOpt_optimizedApkIsInputToApkBuilderAction() throws Exception { useConfiguration(""--experimental_android_resource_path_shortening"", ""-c"", ""opt""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); SpawnAction optimizeAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""app_optimized.ap_"")); assertThat(optimizeAction.getMnemonic()).isEqualTo(""Aapt2Optimize""); assertThat(getGeneratingAction(getFirstArtifactEndingWith(artifacts, ""app_resource_paths.map""))) .isEqualTo(optimizeAction); List processingArgs = optimizeAction.getArguments(); assertThat(processingArgs).contains(""--shorten-resource-paths""); assertThat(flagValue(""--resource-path-shortening-map"", processingArgs)) .endsWith(""app_resource_paths.map""); // Verify that the optimized APK is an input to build the unsigned APK. SpawnAction apkAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""app_unsigned.apk"")); assertThat(apkAction.getMnemonic()).isEqualTo(""ApkBuilder""); assertThat(hasInput(apkAction, ""app_optimized.ap_"")).isTrue(); } @Test public void testResourcePathShortening_flagEnabledAndCDefault_optimizeArtifactsAbsent() throws Exception { useConfiguration(""--experimental_android_resource_path_shortening""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); Artifact resourcePathShorteningMapArtifact = getFirstArtifactEndingWith(artifacts, ""app_resource_paths.map""); assertThat(resourcePathShorteningMapArtifact).isNull(); Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, ""app_optimized.ap_""); assertThat(optimizedResourceApk).isNull(); // Verify that the optimized APK is not an input to build the unsigned APK. SpawnAction apkAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""app_unsigned.apk"")); assertThat(apkAction.getMnemonic()).isEqualTo(""ApkBuilder""); assertThat(hasInput(apkAction, ""app_optimized.ap_"")).isFalse(); } @Test public void testResourcePathShortening_flagNotEnabledAndCOpt_optimizeArtifactsAbsent() throws Exception { useConfiguration(""-c"", ""opt""); ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); Artifact resourcePathShorteningMapArtifact = getFirstArtifactEndingWith(artifacts, ""app_resource_paths.map""); assertThat(resourcePathShorteningMapArtifact).isNull(); Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, ""app_optimized.ap_""); assertThat(optimizedResourceApk).isNull(); // Verify that the optimized APK is not an input to build the unsigned APK. SpawnAction apkAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""app_unsigned.apk"")); assertThat(apkAction.getMnemonic()).isEqualTo(""ApkBuilder""); assertThat(hasInput(apkAction, ""app_optimized.ap_"")).isFalse(); } @Test public void resourceNameCollapse_flagAndProguardSpecsPresent_optimizedApkIsInputToApkBuilder() throws Exception { useConfiguration(""--experimental_android_resource_name_obfuscation""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" shrink_resources = 1,"", "" proguard_specs = ['proguard-spec.pro'],)""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:hello""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); SpawnAction shrinkerAction = getGeneratingSpawnAction( getFirstArtifactEndingWith(artifacts, ""resource_optimization.cfg"")); assertThat(shrinkerAction.getMnemonic()).isEqualTo(""ResourceShrinker""); SpawnAction optimizeAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""hello_optimized.ap_"")); assertThat(optimizeAction.getMnemonic()).isEqualTo(""Aapt2Optimize""); List processingArgs = optimizeAction.getArguments(); assertThat(processingArgs).contains(""--collapse-resource-names""); assertThat(flagValue(""--resources-config-path"", processingArgs)) .endsWith(""resource_optimization.cfg""); // Verify that the optimized APK is an input to build the unsigned APK. SpawnAction apkAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""hello_unsigned.apk"")); assertThat(apkAction.getMnemonic()).isEqualTo(""ApkBuilder""); assertThat(hasInput(apkAction, ""hello_optimized.ap_"")).isTrue(); } @Test public void resourceNameCollapse_featureAndProguardSpecsPresent_optimizedApkIsInputToApkBuilder() throws Exception { useConfiguration(""--features=resource_name_obfuscation""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" shrink_resources = 1,"", "" proguard_specs = ['proguard-spec.pro'],)""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:hello""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); SpawnAction shrinkerAction = getGeneratingSpawnAction( getFirstArtifactEndingWith(artifacts, ""resource_optimization.cfg"")); assertThat(shrinkerAction.getMnemonic()).isEqualTo(""ResourceShrinker""); SpawnAction optimizeAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""hello_optimized.ap_"")); assertThat(optimizeAction.getMnemonic()).isEqualTo(""Aapt2Optimize""); List processingArgs = optimizeAction.getArguments(); assertThat(processingArgs).contains(""--collapse-resource-names""); assertThat(flagValue(""--resources-config-path"", processingArgs)) .endsWith(""resource_optimization.cfg""); // Verify that the optimized APK is an input to build the unsigned APK. SpawnAction apkAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""hello_unsigned.apk"")); assertThat(apkAction.getMnemonic()).isEqualTo(""ApkBuilder""); assertThat(hasInput(apkAction, ""hello_optimized.ap_"")).isTrue(); } @Test public void resourceNameCollapse_flagPresentProguardSpecsAbsent_optimizeArtifactsAbsent() throws Exception { useConfiguration(""--experimental_android_resource_name_obfuscation""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" shrink_resources = 1,)""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:hello""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, ""hello_optimized.ap_""); assertThat(optimizedResourceApk).isNull(); // Verify that the optimized APK is not an input to build the unsigned APK. SpawnAction apkAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""hello_unsigned.apk"")); assertThat(apkAction.getMnemonic()).isEqualTo(""ApkBuilder""); assertThat(hasInput(apkAction, ""hello_optimized.ap_"")).isFalse(); } @Test public void resourceNameCollapse_flagAbsentProguardSpecsPresent_optimizeArtifactsAbsent() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" shrink_resources = 1,"", "" proguard_specs = ['proguard-spec.pro'],)""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:hello""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); Artifact optimizedResourceApk = getFirstArtifactEndingWith(artifacts, ""hello_optimized.ap_""); assertThat(optimizedResourceApk).isNull(); // Verify that the optimized APK is not an input to build the unsigned APK. SpawnAction apkAction = getGeneratingSpawnAction(getFirstArtifactEndingWith(artifacts, ""hello_unsigned.apk"")); assertThat(apkAction.getMnemonic()).isEqualTo(""ApkBuilder""); assertThat(hasInput(apkAction, ""hello_optimized.ap_"")).isFalse(); } @Test public void testResourceShrinking_requiresProguard() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" shrink_resources = 1,)""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:hello""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); assertThat(artifacts) .containsNoneOf( getFirstArtifactEndingWith(artifacts, ""shrunk.jar""), getFirstArtifactEndingWith(artifacts, ""shrunk.ap_"")); } @Test public void testProguardExtraOutputs() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro'])""); ConfiguredTarget output = getConfiguredTarget(""//java/com/google/android/hello:b""); // Checks that ProGuard is called with the appropriate options. Artifact a = getFirstArtifactEndingWith(getFilesToBuild(output), ""_proguard.jar""); SpawnAction action = getGeneratingSpawnAction(a); List args = getGeneratingSpawnActionArgs(a); // Assert that the ProGuard executable set in the android_sdk rule appeared in the command-line // of the SpawnAction that generated the _proguard.jar. assertThat(args) .containsAtLeast( getProguardBinary().getExecPathString(), ""-injars"", execPathEndingWith(action.getInputs(), ""b_deploy.jar""), ""-printseeds"", execPathEndingWith(action.getOutputs(), ""b_proguard.seeds""), ""-printusage"", execPathEndingWith(action.getOutputs(), ""b_proguard.usage"")) .inOrder(); // Checks that the output files are produced. assertProguardUsed(output); assertThat(getBinArtifact(""b_proguard.usage"", output)).isNotNull(); assertThat(getBinArtifact(""b_proguard.seeds"", output)).isNotNull(); } @Test public void testProGuardExecutableMatchesConfiguration() throws Exception { scratch.file( ""java/com/google/devtools/build/jkrunchy/BUILD"", ""package(default_visibility=['//visibility:public'])"", ""java_binary(name = 'jkrunchy',"", "" main_class = 'com.google.devtools.build.jkrunchy.JKrunchyMain')""); useConfiguration(""--proguard_top=//java/com/google/devtools/build/jkrunchy:jkrunchy""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro'])""); ConfiguredTarget output = getConfiguredTarget(""//java/com/google/android/hello:b_proguard.jar""); assertProguardUsed(output); SpawnAction proguardAction = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith(getFilesToBuild(output), ""_proguard.jar""); Artifact jkrunchyExecutable = getExecConfiguredTarget(""//java/com/google/devtools/build/jkrunchy"") .getProvider(FilesToRunProvider.class) .getExecutable(); assertWithMessage(""ProGuard implementation was not correctly taken from the configuration"") .that(proguardAction.getCommandFilename()) .endsWith(jkrunchyExecutable.getRepositoryRelativePathString()); } @Test public void enforceProguardFileExtension_disabled_allowsOtherExtensions() throws Exception { useConfiguration(""--noenforce_proguard_file_extension""); scratch.file( ""java/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro'])""); ConfiguredTarget unused = getConfiguredTarget(""//java/android/hello:b""); } @Test public void enforceProguardFileExtension_enabled_disallowsOtherExtensions() throws Exception { useConfiguration(""--enforce_proguard_file_extension""); scratch.file( ""java/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro'])""); AssertionError assertionError = assertThrows(AssertionError.class, () -> getConfiguredTarget(""//java/android/hello:b"")); assertThat(assertionError).hasMessageThat().contains(""These files do not end in .pgcfg""); } @Test public void enforceProguardFileExtension_enabled_allowsPgcfg() throws Exception { useConfiguration(""--enforce_proguard_file_extension""); scratch.file( ""java/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = [':proguard.pgcfg'])""); ConfiguredTarget unused = getConfiguredTarget(""//java/android/hello:b""); } @Test public void enforceProguardFileExtension_enabled_ignoresThirdParty() throws Exception { useConfiguration(""--enforce_proguard_file_extension""); scratch.file( ""third_party/bar/BUILD"", ""licenses(['unencumbered'])"", ""exports_files(['proguard.pro'])""); scratch.file( ""java/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['//third_party/bar:proguard.pro'])""); ConfiguredTarget unused = getConfiguredTarget(""//java/android/hello:b""); } @Test public void testNeverlinkTransitivity() throws Exception { useConfiguration(""--android_fixed_resource_neverlinking""); scratch.file( ""java/com/google/android/neversayneveragain/BUILD"", ""android_library(name = 'l1',"", "" srcs = ['l1.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/resource.xml'])"", ""android_library(name = 'l2',"", "" srcs = ['l2.java'],"", "" deps = [':l1'],"", "" neverlink = 1)"", ""android_library(name = 'l3',"", "" srcs = ['l3.java'],"", "" deps = [':l2'])"", ""android_library(name = 'l4',"", "" srcs = ['l4.java'],"", "" deps = [':l1'])"", ""android_binary(name = 'b1',"", "" srcs = ['b1.java'],"", "" deps = [':l2'],"", "" manifest = 'AndroidManifest.xml')"", ""android_binary(name = 'b2',"", "" srcs = ['b2.java'],"", "" deps = [':l3'],"", "" manifest = 'AndroidManifest.xml')"", ""android_binary(name = 'b3',"", "" srcs = ['b3.java'],"", "" deps = [':l3', ':l4'],"", "" manifest = 'AndroidManifest.xml')""); ConfiguredTarget b1 = getConfiguredTarget(""//java/com/google/android/neversayneveragain:b1""); Action b1DeployAction = actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(b1)), ""b1_deploy.jar""); List b1Inputs = prettyArtifactNames(b1DeployAction.getInputs()); assertThat(b1Inputs) .containsNoneOf( ""java/com/google/android/neversayneveragain/libl1.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/libl2.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/libl3.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/libl4.jar_desugared.jar""); assertThat(b1Inputs) .contains(""java/com/google/android/neversayneveragain/libb1.jar_desugared.jar""); assertThat( resourceInputPaths( ""java/com/google/android/neversayneveragain"", getValidatedResources(b1))) .doesNotContain(""res/values/resource.xml""); ConfiguredTarget b2 = getConfiguredTarget(""//java/com/google/android/neversayneveragain:b2""); Action b2DeployAction = actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(b2)), ""b2_deploy.jar""); List b2Inputs = prettyArtifactNames(b2DeployAction.getInputs()); assertThat(b2Inputs) .containsNoneOf( ""java/com/google/android/neversayneveragain/libl1.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/libl2.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/libl4.jar_desugared.jar""); assertThat(b2Inputs) .containsAtLeast( ""java/com/google/android/neversayneveragain/_dx/l3/libl3.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/libb2.jar_desugared.jar""); assertThat( resourceInputPaths( ""java/com/google/android/neversayneveragain"", getValidatedResources(b2))) .doesNotContain(""res/values/resource.xml""); ConfiguredTarget b3 = getConfiguredTarget(""//java/com/google/android/neversayneveragain:b3""); Action b3DeployAction = actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(b3)), ""b3_deploy.jar""); List b3Inputs = prettyArtifactNames(b3DeployAction.getInputs()); assertThat(b3Inputs) .containsAtLeast( ""java/com/google/android/neversayneveragain/_dx/l1/libl1.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/_dx/l3/libl3.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/_dx/l4/libl4.jar_desugared.jar"", ""java/com/google/android/neversayneveragain/libb3.jar_desugared.jar""); assertThat(b3Inputs) .doesNotContain(""java/com/google/android/neversayneveragain/libl2.jar_desugared.jar""); assertThat( resourceInputPaths( ""java/com/google/android/neversayneveragain"", getValidatedResources(b3))) .contains(""res/values/resource.xml""); } @Test public void testDexopts() throws Exception { useConfiguration(""--noincremental_dexing""); checkDexopts(""[ '--opt1', '--opt2' ]"", ImmutableList.of(""--opt1"", ""--opt2"")); } @Test public void testDexoptsTokenization() throws Exception { useConfiguration(""--noincremental_dexing""); checkDexopts( ""[ '--opt1', '--opt2 tokenized' ]"", ImmutableList.of(""--opt1"", ""--opt2"", ""tokenized"")); } @Test public void testDexoptsMakeVariableSubstitution() throws Exception { useConfiguration(""--noincremental_dexing""); checkDexopts(""[ '--opt1', '$(COMPILATION_MODE)' ]"", ImmutableList.of(""--opt1"", ""fastbuild"")); } private void checkDexopts(String dexopts, List expectedArgs) throws Exception { scratch.file( ""java/com/google/android/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['dummy1.java'],"", "" dexopts = "" + dexopts + "","", "" manifest = 'AndroidManifest.xml')""); // Include arguments that are always included. ImmutableList fixedArgs = ImmutableList.of(""--multi-dex""); expectedArgs = new ImmutableList.Builder().addAll(expectedArgs).addAll(fixedArgs).build(); // Ensure that the args that immediately follow ""--dex"" match the expectation. ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android:b""); List args = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""intermediate_classes.dex.zip"")); int start = args.indexOf(""--dex"") + 1; assertThat(start).isNotEqualTo(0); int end = Math.min(args.size(), start + expectedArgs.size()); assertThat(args.subList(start, end)).isEqualTo(expectedArgs); } @Test public void testDexMainListOpts() throws Exception { checkDexMainListOpts(""[ '--opt1', '--opt2' ]"", ""--opt1"", ""--opt2""); } @Test public void testDexMainListOptsTokenization() throws Exception { checkDexMainListOpts(""[ '--opt1', '--opt2 tokenized' ]"", ""--opt1"", ""--opt2"", ""tokenized""); } @Test public void testDexMainListOptsMakeVariableSubstitution() throws Exception { checkDexMainListOpts(""[ '--opt1', '$(COMPILATION_MODE)' ]"", ""--opt1"", ""fastbuild""); } private void checkDexMainListOpts(String mainDexListOpts, String... expectedArgs) throws Exception { scratch.file( ""java/com/google/android/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['dummy1.java'],"", "" multidex = \""legacy\"","", "" main_dex_list_opts = "" + mainDexListOpts + "","", "" manifest = 'AndroidManifest.xml')""); // Ensure that the args that immediately follow the main class in the shell command // match the expectation. ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android:b""); List args = getGeneratingSpawnActionArgs( ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""main_dex_list.txt"")); // args: [ ""bash"", ""-c"", ""java -cp dx.jar main opts other"" ] MoreAsserts.assertContainsSublist(args, expectedArgs); } @Test public void omitResourcesInfoProviderFromAndroidBinary_enabled() throws Exception { useConfiguration(""--experimental_omit_resources_info_provider_from_android_binary""); ConfiguredTarget binary = scratchConfiguredTarget( ""java/com/pkg/myapp"", ""myapp"", ""android_binary("", "" name = 'myapp',"", "" manifest = 'AndroidManifest.xml',"", "")""); assertThat(binary.get(AndroidResourcesInfo.PROVIDER)).isNull(); } @Test public void omitResourcesInfoProviderFromAndroidBinary_disabled() throws Exception { useConfiguration(""--noexperimental_omit_resources_info_provider_from_android_binary""); ConfiguredTarget binary = scratchConfiguredTarget( ""java/com/pkg/myapp"", ""myapp"", ""android_binary("", "" name = 'myapp',"", "" manifest = 'AndroidManifest.xml',"", "")""); assertThat(binary.get(AndroidResourcesInfo.PROVIDER)).isNotNull(); } @Test public void testResourceConfigurationFilters() throws Exception { scratch.file( ""java/com/google/android/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['dummy1.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_configuration_filters = [ 'en', 'fr'],)""); // Ensure that the args are present ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android:b""); List args = resourceArguments(getValidatedResources(binary)); assertThat(flagValue(""--resourceConfigs"", args)).contains(""en,fr""); } /** Test that resources are not filtered in analysis under aapt2. */ @Test public void testFilteredResourcesFilteringAapt2() throws Exception { List resources = ImmutableList.of(""res/values/foo.xml"", ""res/values-en/foo.xml"", ""res/values-fr/foo.xml""); String dir = ""java/r/android""; ConfiguredTarget binary = scratchConfiguredTarget( dir, ""r"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_configuration_filters = ['', 'en, es, '],"", "" densities = ['hdpi, , ', 'xhdpi'],"", "" resource_files = ['"" + Joiner.on(""', '"").join(resources) + ""'])""); ValidatedAndroidResources directResources = getValidatedResources(binary, /* transitive= */ false); // Validate that the AndroidResourceProvider for this binary contains all values. assertThat(resourceContentsPaths(dir, directResources)).containsExactlyElementsIn(resources); // Validate that the input to resource processing contains all values. assertThat(resourceInputPaths(dir, directResources)).containsAtLeastElementsIn(resources); // Validate that the filters are correctly passed to the resource processing action // This includes trimming whitespace and ignoring empty filters. assertThat(resourceArguments(directResources)).contains(""en,es""); assertThat(resourceArguments(directResources)).contains(""hdpi,xhdpi""); } @Test public void testFilterResourcesPseudolocalesPropagated() throws Exception { String dir = ""java/r/android""; ConfiguredTarget binary = scratchConfiguredTarget( dir, ""bin"", ""android_binary(name = 'bin',"", "" resource_configuration_filters = ['en', 'en-rXA', 'ar-rXB'],"", "" manifest = 'AndroidManifest.xml')""); List resourceProcessingArgs = getGeneratingSpawnActionArgs(getValidatedResources(binary).getRTxt()); assertThat(resourceProcessingArgs).containsAtLeast(""--resourceConfigs"", ""ar-rXB,en,en-rXA""); } /** * Gets the paths of matching artifacts contained within a resource container * * @param dir the directory to look for artifacts in * @param resource the container that contains eligible artifacts * @return the paths to all artifacts from the input that are contained within the given * directory, relative to that directory. */ private List resourceContentsPaths(String dir, ValidatedAndroidResources resource) { return pathsToArtifacts(dir, resource.getArtifacts()); } /** * Gets the paths of matching artifacts that are used as input to resource processing * * @param dir the directory to look for artifacts in * @param resource the output from the resource processing that uses these artifacts as inputs * @return the paths to all artifacts used as inputs to resource processing that are contained * within the given directory, relative to that directory. */ private List resourceInputPaths(String dir, ValidatedAndroidResources resource) { return pathsToArtifacts(dir, resourceGeneratingAction(resource).getInputs().toList()); } /** * Gets the paths of matching artifacts from an iterable * * @param dir the directory to look for artifacts in * @param artifacts all available artifacts * @return the paths to all artifacts from the input that are contained within the given * directory, relative to that directory. */ private List pathsToArtifacts(String dir, Iterable artifacts) { List paths = new ArrayList<>(); Path containingDir = rootDirectory; for (String part : dir.split(""/"")) { containingDir = containingDir.getChild(part); } for (Artifact a : artifacts) { if (a.getPath().startsWith(containingDir)) { paths.add(a.getPath().relativeTo(containingDir).toString()); } } return paths; } @Test public void testInheritedRNotInRuntimeJars() throws Exception { String dir = ""java/r/android/""; scratch.file( dir + ""BUILD"", ""android_library(name = 'sublib',"", "" manifest = 'AndroidManifest.xml',"", "" srcs =['sublib.java'],"", "" )"", ""android_library(name = 'lib',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':sublib'],"", "" srcs =['lib.java'],"", "" )"", ""android_binary(name = 'bin',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':lib'],"", "" srcs =['bin.java'],"", "" )""); Action deployJarAction = getGeneratingAction( getFileConfiguredTarget(""//java/r/android:bin_deploy.jar"").getArtifact()); List inputs = ActionsTestUtil.baseArtifactNames(deployJarAction.getInputs()); assertThat(inputs) .containsAtLeast( ""libsublib.jar_desugared.jar"", ""liblib.jar_desugared.jar"", ""libbin.jar_desugared.jar"", ""bin_resources.jar_desugared.jar""); assertThat(inputs) .containsNoneOf(""lib_resources.jar_desugared.jar"", ""sublib_resources.jar_desugared.jar""); } @Test public void testLocalResourcesUseRClassGenerator() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_library(name = 'lib',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res2/**']),"", "" )"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" deps = [':lib'],"", "" )""); scratch.file( ""java/r/android/res2/values/strings.xml"", ""Libs!""); scratch.file( ""java/r/android/res/values/strings.xml"", ""Hello Android!""); Artifact jar = getResourceClassJar(getConfiguredTargetAndData(""//java/r/android:r"")); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); assertThat(getGeneratingSpawnActionArgs(jar)) .containsAtLeast(""--primaryRTxt"", ""--primaryManifest"", ""--library"", ""--classJarOutput""); } @Test public void testLocalResourcesUseRClassGeneratorNoLibraries() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" )""); scratch.file( ""java/r/android/res/values/strings.xml"", ""Hello Android!""); Artifact jar = getResourceClassJar(getConfiguredTargetAndData(""//java/r/android:r"")); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); List args = getGeneratingSpawnActionArgs(jar); assertThat(args).containsAtLeast(""--primaryRTxt"", ""--primaryManifest"", ""--classJarOutput""); assertThat(args).doesNotContain(""--libraries""); } @Test public void testUseRClassGeneratorCustomPackage() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_library(name = 'lib',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res2/**']),"", "" custom_package = 'com.lib.custom',"", "" )"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" custom_package = 'com.binary.custom',"", "" deps = [':lib'],"", "" )""); scratch.file( ""java/r/android/res2/values/strings.xml"", ""Libs!""); scratch.file( ""java/r/android/res/values/strings.xml"", ""Hello Android!""); ConfiguredTargetAndData binary = getConfiguredTargetAndData(""//java/r/android:r""); Artifact jar = getResourceClassJar(binary); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); List args = getGeneratingSpawnActionArgs(jar); assertThat(args) .containsAtLeast( ""--primaryRTxt"", ""--primaryManifest"", ""--library"", ""--classJarOutput"", ""--packageForR"", ""com.binary.custom""); } @Test public void testUseRClassGeneratorMultipleDeps() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_library(name = 'lib1',"", "" manifest = 'AndroidManifest.xml',"", "" )"", ""android_library(name = 'lib2',"", "" manifest = 'AndroidManifest.xml',"", "" )"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':lib1', ':lib2'],"", "" )""); ConfiguredTargetAndData binary = getConfiguredTargetAndData(""//java/r/android:r""); Artifact jar = getResourceClassJar(binary); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); List args = getGeneratingSpawnActionArgs(jar); AndroidResourcesInfo resourcesInfo = binary.getConfiguredTarget().get(AndroidResourcesInfo.PROVIDER); assertThat(resourcesInfo.getTransitiveAndroidResources().toList()).hasSize(2); ValidatedAndroidResources firstDep = resourcesInfo.getTransitiveAndroidResources().toList().get(0); ValidatedAndroidResources secondDep = resourcesInfo.getTransitiveAndroidResources().toList().get(1); assertThat(args) .containsAtLeast( ""--primaryRTxt"", ""--primaryManifest"", ""--library"", firstDep.getAapt2RTxt().getExecPathString() + "","" + firstDep.getManifest().getExecPathString(), ""--library"", secondDep.getAapt2RTxt().getExecPathString() + "","" + secondDep.getManifest().getExecPathString(), ""--classJarOutput"") .inOrder(); } @Test public void useRTxtFromMergedResourcesForFinalRClasses() throws Exception { useConfiguration(""--experimental_use_rtxt_from_merged_resources""); scratch.file( ""java/pkg/BUILD"", ""android_library("", "" name = 'lib',"", "" srcs = ['B.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [ 'res/values/values.xml' ], "", "")"", ""android_binary("", "" name = 'bin',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [ ':lib' ],"", "")""); ConfiguredTargetAndData bin = getConfiguredTargetAndData(""//java/pkg:bin""); ConfiguredTarget lib = getDirectPrerequisite(bin.getConfiguredTarget(), ""//java/pkg:lib""); ValidatedAndroidResources libResources = lib.get(AndroidResourcesInfo.PROVIDER).getDirectAndroidResources().toList().get(0); SpawnAction [MASK] = getGeneratingSpawnAction(getResourceClassJar(bin)); // verify that the R.txt from creating the library-level resources.jar is also used for creating // the top-level resources.jar assertThat(getGeneratingSpawnAction(libResources.getClassJar()).getOutputs()) .contains(libResources.getAapt2RTxt()); MoreAsserts.assertContainsSublist( [MASK] .getArguments(), ""--library"", libResources.getAapt2RTxt().getExecPathString() + "","" + libResources.getManifest().getExecPathString()); // the ""validation artifact"" shouldn't be used for creating the top-level resources.jar, // but it's still fed as a pseudo-input to trigger validation. MoreAsserts.assertDoesNotContainSublist( [MASK] .getArguments(), ""--library"", libResources.getAapt2ValidationArtifact().getExecPathString() + "","" + libResources.getManifest().getExecPathString()); assertThat( [MASK] .getInputs().toList()) .contains(libResources.getAapt2ValidationArtifact()); } // (test for undesired legacy behavior) @Test public void doNotUseRTxtFromMergedResourcesForFinalRClasses() throws Exception { scratch.file( ""java/pkg/BUILD"", ""android_library("", "" name = 'lib',"", "" srcs = ['B.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [ 'res/values/values.xml' ], "", "")"", ""android_binary("", "" name = 'bin',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [ ':lib' ],"", "")""); ConfiguredTargetAndData bin = getConfiguredTargetAndData(""//java/pkg:bin""); ConfiguredTarget lib = getDirectPrerequisite(bin.getConfiguredTarget(), ""//java/pkg:lib""); ValidatedAndroidResources libResources = lib.get(AndroidResourcesInfo.PROVIDER).getDirectAndroidResources().toList().get(0); // use the ""validation artifact"" for creating the top-level resources.jar MoreAsserts.assertContainsSublist( getGeneratingSpawnActionArgs(getResourceClassJar(bin)), ""--library"", libResources.getAapt2ValidationArtifact().getExecPathString() + "","" + libResources.getManifest().getExecPathString()); } @Test public void testNoCrunchBinaryOnly() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/drawable-hdpi-v4/foo.png',"", "" 'res/drawable-hdpi-v4/bar.9.png'],"", "" crunch_png = 0,"", "" )""); ConfiguredTarget binary = getConfiguredTarget(""//java/r/android:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(binary)); assertThat(args).contains(""--useAaptCruncher=no""); } @Test public void testDoCrunch() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/drawable-hdpi-v4/foo.png',"", "" 'res/drawable-hdpi-v4/bar.9.png'],"", "" crunch_png = 1,"", "" )""); ConfiguredTarget binary = getConfiguredTarget(""//java/r/android:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(binary)); assertThat(args).doesNotContain(""--useAaptCruncher=no""); } @Test public void testDoCrunchDefault() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/drawable-hdpi-v4/foo.png',"", "" 'res/drawable-hdpi-v4/bar.9.png'],"", "" )""); ConfiguredTarget binary = getConfiguredTarget(""//java/r/android:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(binary)); assertThat(args).doesNotContain(""--useAaptCruncher=no""); } @Test public void testNoCrunchWithAndroidLibraryNoBinaryResources() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_library(name = 'resources',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml',"", "" 'res/drawable-hdpi-v4/foo.png',"", "" 'res/drawable-hdpi-v4/bar.9.png'],"", "" )"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':resources'],"", "" crunch_png = 0,"", "" )""); ConfiguredTarget binary = getConfiguredTarget(""//java/r/android:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(binary)); assertThat(args).contains(""--useAaptCruncher=no""); } @Test public void testNoCrunchWithMultidexNative() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_library(name = 'resources',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml',"", "" 'res/drawable-hdpi-v4/foo.png',"", "" 'res/drawable-hdpi-v4/bar.9.png'],"", "" )"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':resources'],"", "" multidex = 'native',"", "" crunch_png = 0,"", "" )""); ConfiguredTarget binary = getConfiguredTarget(""//java/r/android:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(binary)); assertThat(args).contains(""--useAaptCruncher=no""); } @Test public void testZipaligned() throws Exception { ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); Artifact a = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""zipaligned_app.apk""); SpawnAction action = getGeneratingSpawnAction(a); assertThat(action.getMnemonic()).isEqualTo(""AndroidZipAlign""); List arguments = getGeneratingSpawnActionArgs(a); assertThat(arguments).contains(""-p""); assertThat(arguments).contains(""4""); Artifact zipAlignTool = getFirstArtifactEndingWith(action.getInputs(), ""/zipalign""); assertThat(arguments).contains(zipAlignTool.getExecPathString()); Artifact unsignedApk = getFirstArtifactEndingWith(action.getInputs(), ""/app_unsigned.apk""); assertThat(arguments).contains(unsignedApk.getExecPathString()); Artifact zipalignedApk = getFirstArtifactEndingWith(action.getOutputs(), ""/zipaligned_app.apk""); assertThat(arguments).contains(zipalignedApk.getExecPathString()); } @Test public void testDeployInfo() throws Exception { ConfiguredTarget binary = getConfiguredTarget(""//java/android:app""); NestedSet outputGroup = getOutputGroup(binary, ""android_deploy_info""); Artifact deployInfoArtifact = ActionsTestUtil.getFirstArtifactEndingWith(outputGroup, ""/deploy_info.deployinfo.pb""); assertThat(deployInfoArtifact).isNotNull(); AndroidDeployInfo deployInfo = getAndroidDeployInfo(deployInfoArtifact); assertThat(deployInfo).isNotNull(); assertThat(deployInfo.getMergedManifest().getExecRootPath()).endsWith(""/AndroidManifest.xml""); assertThat(deployInfo.getAdditionalMergedManifestsList()).isEmpty(); assertThat(deployInfo.getApksToDeploy(0).getExecRootPath()).endsWith(""/app.apk""); Artifact mergedManifest = ActionsTestUtil.getFirstArtifactEndingWith(outputGroup, ""/AndroidManifest.xml""); assertThat(mergedManifest).isNotNull(); } /** * Internal helper method: checks that dex sharding input and output is correct for different * combinations of multidex mode and build with and without proguard. */ private void internalTestDexShardStructure( MultidexMode multidexMode, boolean proguard, String nonProguardSuffix) throws Exception { ConfiguredTarget target = getConfiguredTarget(""//java/a:a""); assertNoEvents(); Action shardAction = getGeneratingAction(getBinArtifact(""_dx/a/shard1.jar"", target)); // Verify command line arguments List arguments = ((SpawnAction) shardAction).getRemainingArguments(); List expectedArguments = new ArrayList<>(); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(target)); Artifact shard1 = getFirstArtifactEndingWith(artifacts, ""shard1.jar""); Artifact shard2 = getFirstArtifactEndingWith(artifacts, ""shard2.jar""); Artifact resourceJar = getFirstArtifactEndingWith(artifacts, ""/java_resources.jar""); expectedArguments.add(""--output_jar""); expectedArguments.add(shard1.getExecPathString()); expectedArguments.add(""--output_jar""); expectedArguments.add(shard2.getExecPathString()); expectedArguments.add(""--output_resources""); expectedArguments.add(resourceJar.getExecPathString()); if (multidexMode == MultidexMode.LEGACY) { Artifact mainDexList = getFirstArtifactEndingWith(artifacts, ""main_dex_list.txt""); expectedArguments.add(""--main_dex_filter""); expectedArguments.add(mainDexList.getExecPathString()); } if (!proguard) { expectedArguments.add(""--input_jar""); expectedArguments.add( getFirstArtifactEndingWith(artifacts, ""a_resources.jar"" + nonProguardSuffix) .getExecPathString()); } Artifact inputJar; if (proguard) { inputJar = getFirstArtifactEndingWith(artifacts, ""a_proguard.jar""); } else { inputJar = getFirstArtifactEndingWith(artifacts, ""liba.jar"" + nonProguardSuffix); } expectedArguments.add(""--input_jar""); expectedArguments.add(inputJar.getExecPathString()); assertThat(arguments).containsExactlyElementsIn(expectedArguments).inOrder(); // Verify input and output artifacts List shardOutputs = ActionsTestUtil.baseArtifactNames(shardAction.getOutputs()); List shardInputs = ActionsTestUtil.baseArtifactNames(shardAction.getInputs()); assertThat(shardOutputs).containsExactly(""shard1.jar"", ""shard2.jar"", ""java_resources.jar""); if (multidexMode == MultidexMode.LEGACY) { assertThat(shardInputs).contains(""main_dex_list.txt""); } else { assertThat(shardInputs).doesNotContain(""main_dex_list.txt""); } if (proguard) { assertThat(shardInputs).contains(""a_proguard.jar""); assertThat(shardInputs).doesNotContain(""liba.jar"" + nonProguardSuffix); } else { assertThat(shardInputs).contains(""liba.jar"" + nonProguardSuffix); assertThat(shardInputs).doesNotContain(""a_proguard.jar""); } assertThat(shardInputs).doesNotContain(""a_deploy.jar""); // Verify that dex compilation is followed by the correct merge operation Action apkAction = getGeneratingAction( getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), ""compressed_a_unsigned.apk"")); Action mergeAction = getGeneratingAction(getFirstArtifactEndingWith(apkAction.getInputs(), ""classes.dex.zip"")); Iterable dexShards = Iterables.filter( mergeAction.getInputs().toList(), ActionsTestUtil.getArtifactSuffixMatcher("".dex.zip"")); assertThat(ActionsTestUtil.baseArtifactNames(dexShards)) .containsExactly(""shard1.dex.zip"", ""shard2.dex.zip""); } @Test public void testDexShardingDoesNotWorkWithManualMultidex() throws Exception { scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='manual_main_dex',"", "" main_dex_list='main_dex_list.txt',"", "" manifest='AndroidManifest.xml')""); reporter.removeHandler(failFastHandler); getConfiguredTarget(""//java/a:a""); assertContainsEvent("".dex sharding is not available in manual multidex mode""); } @Test public void testDexShardingLegacyStructure() throws Exception { useConfiguration(""--noincremental_dexing""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='legacy',"", "" manifest='AndroidManifest.xml')""); internalTestDexShardStructure(MultidexMode.LEGACY, false, ""_desugared.jar""); } @Test public void testDexShardingNativeStructure_withNoDesugaring() throws Exception { useConfiguration(""--noexperimental_desugar_for_android"", ""--noincremental_dexing""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='native',"", "" manifest='AndroidManifest.xml')""); internalTestDexShardStructure(MultidexMode.NATIVE, false, """"); } @Test public void testDexShardingNativeStructure() throws Exception { useConfiguration(""--noincremental_dexing""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='native',"", "" manifest='AndroidManifest.xml')""); internalTestDexShardStructure(MultidexMode.NATIVE, false, ""_desugared.jar""); } @Test public void testDexShardingLegacyAndProguardStructure_withNoDesugaring() throws Exception { useConfiguration( ""--noexperimental_desugar_for_android"", ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='legacy',"", "" manifest='AndroidManifest.xml',"", "" proguard_specs=['proguard.cfg'])""); internalTestDexShardStructure(MultidexMode.LEGACY, true, """"); } @Test public void testDexShardingLegacyAndProguardStructure() throws Exception { useConfiguration( ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='legacy',"", "" manifest='AndroidManifest.xml',"", "" proguard_specs=['proguard.cfg'])""); internalTestDexShardStructure(MultidexMode.LEGACY, true, ""_desugared.jar""); } @Test public void testDexShardingNativeAndProguardStructure() throws Exception { useConfiguration( ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='native',"", "" manifest='AndroidManifest.xml',"", "" proguard_specs=['proguard.cfg'])""); internalTestDexShardStructure(MultidexMode.NATIVE, true, """"); } @Test public void testIncrementalApkAndProguardBuildStructure() throws Exception { scratch.file( ""java/a/BUILD"", ""android_binary("", "" name='a',"", "" srcs=['A.java'],"", "" dex_shards=2,"", "" multidex='native',"", "" manifest='AndroidManifest.xml',"", "" proguard_specs=['proguard.cfg'])""); ConfiguredTarget target = getConfiguredTarget(""//java/a:a""); Action shardAction = getGeneratingAction(getBinArtifact(""_dx/a/shard1.jar"", target)); List shardOutputs = ActionsTestUtil.baseArtifactNames(shardAction.getOutputs()); assertThat(shardOutputs).contains(""java_resources.jar""); assertThat(shardOutputs).doesNotContain(""a_deploy.jar""); } @Test public void testManualMainDexBuildStructure() throws Exception { checkError( ""java/foo"", ""maindex_nomultidex"", ""Both \""main_dex_list\"" and \""multidex='manual_main_dex'\"" must be specified"", ""android_binary("", "" name = 'maindex_nomultidex',"", "" srcs = ['a.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'manual_main_dex')""); } @Test public void testMainDexListLegacyMultidex() throws Exception { checkError( ""java/foo"", ""maindex_nomultidex"", ""Both \""main_dex_list\"" and \""multidex='manual_main_dex'\"" must be specified"", ""android_binary("", "" name = 'maindex_nomultidex',"", "" srcs = ['a.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy',"", "" main_dex_list = 'main_dex_list.txt')""); } @Test public void testMainDexListNativeMultidex() throws Exception { checkError( ""java/foo"", ""maindex_nomultidex"", ""Both \""main_dex_list\"" and \""multidex='manual_main_dex'\"" must be specified"", ""android_binary("", "" name = 'maindex_nomultidex',"", "" srcs = ['a.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "" main_dex_list = 'main_dex_list.txt')""); } @Test public void testMainDexListNoMultidex() throws Exception { checkError( ""java/foo"", ""maindex_nomultidex"", ""Both \""main_dex_list\"" and \""multidex='manual_main_dex'\"" must be specified"", ""android_binary("", "" name = 'maindex_nomultidex',"", "" srcs = ['a.java'],"", "" manifest = 'AndroidManifest.xml',"", "" main_dex_list = 'main_dex_list.txt')""); } @Test public void testMainDexListWithAndroidSdk() throws Exception { scratch.file( ""sdk/BUILD"", ""android_sdk("", "" name = 'sdk',"", "" aapt = 'aapt',"", "" aapt2 = 'aapt2',"", "" adb = 'adb',"", "" aidl = 'aidl',"", "" android_jar = 'android.jar',"", "" apksigner = 'apksigner',"", "" dx = 'dx',"", "" framework_aidl = 'framework_aidl',"", "" main_dex_classes = 'main_dex_classes',"", "" main_dex_list_creator = 'main_dex_list_creator',"", "" proguard = 'proguard',"", "" shrinked_android_jar = 'shrinked_android_jar',"", "" zipalign = 'zipalign',"", "" tags = ['__ANDROID_RULES_MIGRATION__'])""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy',"", "" main_dex_list_opts = ['--hello', '--world'])""); useConfiguration(""--android_sdk=//sdk:sdk""); ConfiguredTarget a = getConfiguredTarget(""//java/a:a""); Artifact mainDexList = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(a)), ""main_dex_list.txt""); List args = getGeneratingSpawnActionArgs(mainDexList); assertThat(args).containsAtLeast(""--hello"", ""--world""); } @Test public void testLegacyMainDexListWithAndroidSdk() throws Exception { scratch.file( ""tools/fake/BUILD"", ""cc_binary("", "" name = 'generate_main_dex_list',"", "" srcs = ['main.cc'])""); scratch.file( ""sdk/BUILD"", ""android_sdk("", "" name = 'sdk',"", "" aapt = 'aapt',"", "" aapt2 = 'aapt2',"", "" adb = 'adb',"", "" aidl = 'aidl',"", "" android_jar = 'android.jar',"", "" apksigner = 'apksigner',"", "" dx = 'dx',"", "" framework_aidl = 'framework_aidl',"", "" main_dex_classes = 'main_dex_classes',"", "" main_dex_list_creator = 'main_dex_list_creator',"", "" proguard = 'proguard',"", "" shrinked_android_jar = 'shrinked_android_jar',"", "" zipalign = 'zipalign',"", "" legacy_main_dex_list_generator = '//tools/fake:generate_main_dex_list',"", "" tags = ['__ANDROID_RULES_MIGRATION__'])""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy')""); useConfiguration(""--android_sdk=//sdk:sdk""); ConfiguredTarget a = getConfiguredTarget(""//java/a:a""); Artifact mainDexList = ActionsTestUtil.getFirstArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(a)), ""main_dex_list.txt""); List args = getGeneratingSpawnActionArgs(mainDexList); NestedSet mainDexInputs = getGeneratingAction(mainDexList).getInputs(); MoreAsserts.assertContainsSublist(args, ""--lib"", ""sdk/android.jar""); assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains(""generate_main_dex_list""); assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains(""a_deploy.jar""); assertThat(getFirstArtifactEndingWith(mainDexInputs, ""main_dex_list_creator"")).isNull(); } @Test public void testMainDexAaptGenerationSupported() throws Exception { useConfiguration(""--android_sdk=//sdk:sdk"", ""--noincremental_dexing""); scratch.file( ""sdk/BUILD"", ""android_sdk("", "" name = 'sdk',"", "" build_tools_version = '24.0.0',"", "" aapt = 'aapt',"", "" aapt2 = 'aapt2',"", "" adb = 'adb',"", "" aidl = 'aidl',"", "" android_jar = 'android.jar',"", "" apksigner = 'apksigner',"", "" dx = 'dx',"", "" framework_aidl = 'framework_aidl',"", "" main_dex_classes = 'main_dex_classes',"", "" main_dex_list_creator = 'main_dex_list_creator',"", "" proguard = 'proguard',"", "" shrinked_android_jar = 'shrinked_android_jar',"", "" zipalign = 'zipalign',"", "" tags = ['__ANDROID_RULES_MIGRATION__'])""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy')""); ConfiguredTargetAndData a = getConfiguredTargetAndData(""//java/a:a""); Artifact intermediateJar = artifactByPath( ImmutableList.of(getCompressedUnsignedApk(a.getConfiguredTarget())), "".apk"", "".dex.zip"", "".dex.zip"", ""main_dex_list.txt"", ""_intermediate.jar""); List args = getGeneratingSpawnActionArgs(intermediateJar); assertContainsSublist( args, ImmutableList.of( ""-include"", a.getConfiguration().getBinFragment(RepositoryName.MAIN) + ""/java/a/proguard/a/main_dex_a_proguard.cfg"")); } @Test public void testMainDexGenerationWithoutProguardMap() throws Exception { useConfiguration(""--noincremental_dexing""); scratchConfiguredTarget( ""java/foo"", ""abin"", ""android_binary("", "" name = 'abin',"", "" srcs = ['a.java'],"", "" proguard_specs = [],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'legacy',)""); ConfiguredTarget a = getConfiguredTarget(""//java/foo:abin""); Artifact intermediateJar = artifactByPath( ImmutableList.of(getCompressedUnsignedApk(a)), "".apk"", "".dex.zip"", "".dex.zip"", ""main_dex_list.txt"", ""_intermediate.jar""); List args = getGeneratingSpawnActionArgs(intermediateJar); MoreAsserts.assertDoesNotContainSublist(args, ""-previousobfuscationmap""); } // regression test for b/14288948 @Test public void testEmptyListAsProguardSpec() throws Exception { scratch.file( ""java/foo/BUILD"", ""android_binary("", "" name = 'abin',"", "" srcs = ['a.java'],"", "" proguard_specs = [],"", "" manifest = 'AndroidManifest.xml')""); Rule rule = getTarget(""//java/foo:abin"").getAssociatedRule(); assertNoEvents(); ImmutableList implicitOutputFilenames = rule.getOutputFiles().stream().map(FileTarget::getName).collect(toImmutableList()); assertThat(implicitOutputFilenames).doesNotContain(""abin_proguard.jar""); } @Test public void testConfigurableProguardSpecsEmptyList() throws Exception { scratch.file( ""java/foo/BUILD"", ""android_binary("", "" name = 'abin',"", "" srcs = ['a.java'],"", "" proguard_specs = select({"", "" '"" + BuildType.Selector.DEFAULT_CONDITION_KEY + ""': [],"", "" }),"", "" manifest = 'AndroidManifest.xml')""); Rule rule = getTarget(""//java/foo:abin"").getAssociatedRule(); assertNoEvents(); ImmutableList implicitOutputFilenames = rule.getOutputFiles().stream().map(FileTarget::getName).collect(toImmutableList()); assertThat(implicitOutputFilenames).contains(""abin_proguard.jar""); } @Test public void testConfigurableProguardSpecsEmptyListWithMapping() throws Exception { scratchConfiguredTarget( ""java/foo"", ""abin"", ""android_binary("", "" name = 'abin',"", "" srcs = ['a.java'],"", "" proguard_specs = select({"", "" '"" + BuildType.Selector.DEFAULT_CONDITION_KEY + ""': [],"", "" }),"", "" proguard_generate_mapping = 1,"", "" manifest = 'AndroidManifest.xml')""); assertNoEvents(); } @Test public void testResourcesWithConfigurationQualifier_localResources() throws Exception { scratch.file( ""java/android/resources/BUILD"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = glob(['res/**']),"", "" )""); scratch.file( ""java/android/resources/res/values-en/strings.xml"", ""Hello Android!""); scratch.file( ""java/android/resources/res/values/strings.xml"", ""Hello Android!""); ConfiguredTarget resource = getConfiguredTarget(""//java/android/resources:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(resource)); assertPrimaryResourceDirs(ImmutableList.of(""java/android/resources/res""), args); } @Test public void testResourcesInOtherPackage_exported_localResources() throws Exception { scratch.file( ""java/android/resources/BUILD"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['//java/resources/other:res/values/strings.xml'],"", "" )""); scratch.file(""java/resources/other/BUILD"", ""exports_files(['res/values/strings.xml'])""); ConfiguredTarget resource = getConfiguredTarget(""//java/android/resources:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(resource)); assertPrimaryResourceDirs(ImmutableList.of(""java/resources/other/res""), args); assertNoEvents(); } @Test public void testResourcesInOtherPackage_filegroup_localResources() throws Exception { scratch.file( ""java/android/resources/BUILD"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['//java/other/resources:fg'],"", "" )""); scratch.file( ""java/other/resources/BUILD"", ""filegroup(name = 'fg',"", "" srcs = ['res/values/strings.xml'],"", "")""); ConfiguredTarget resource = getConfiguredTarget(""//java/android/resources:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(resource)); assertPrimaryResourceDirs(ImmutableList.of(""java/other/resources/res""), args); assertNoEvents(); } @Test public void testResourcesInOtherPackage_filegroupWithExternalSources_localResources() throws Exception { scratch.file( ""java/android/resources/BUILD"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [':fg'],"", "" )"", ""filegroup(name = 'fg',"", "" srcs = ['//java/other/resources:res/values/strings.xml'])""); scratch.file(""java/other/resources/BUILD"", ""exports_files(['res/values/strings.xml'])""); ConfiguredTarget resource = getConfiguredTarget(""//java/android/resources:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(resource)); assertPrimaryResourceDirs(ImmutableList.of(""java/other/resources/res""), args); assertNoEvents(); } @Test public void testMultipleDependentResourceDirectories_localResources() throws Exception { scratch.file( ""java/android/resources/d1/BUILD"", ""android_library(name = 'd1',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['d1-res/values/strings.xml'],"", "" )""); scratch.file( ""java/android/resources/d2/BUILD"", ""android_library(name = 'd2',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['d2-res/values/strings.xml'],"", "" )""); scratch.file( ""java/android/resources/BUILD"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['bin-res/values/strings.xml'],"", "" deps = ["", "" '//java/android/resources/d1:d1','//java/android/resources/d2:d2'"", "" ])""); ConfiguredTarget resource = getConfiguredTarget(""//java/android/resources:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(resource)); assertPrimaryResourceDirs(ImmutableList.of(""java/android/resources/bin-res""), args); assertThat(getDirectDependentResourceDirs(args)) .containsAtLeast(""java/android/resources/d1/d1-res"", ""java/android/resources/d2/d2-res""); assertNoEvents(); } // Regression test for b/11924769 @Test public void testResourcesInOtherPackage_doubleFilegroup_localResources() throws Exception { scratch.file( ""java/android/resources/BUILD"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [':fg'],"", "" )"", ""filegroup(name = 'fg',"", "" srcs = ['//java/other/resources:fg'])""); scratch.file( ""java/other/resources/BUILD"", ""filegroup(name = 'fg',"", "" srcs = ['res/values/strings.xml'],"", "")""); ConfiguredTarget resource = getConfiguredTarget(""//java/android/resources:r""); List args = getGeneratingSpawnActionArgs(getResourceApk(resource)); assertPrimaryResourceDirs(ImmutableList.of(""java/other/resources/res""), args); assertNoEvents(); } @Test public void testResourcesDoesNotMatchDirectoryLayout_badFile_localResources() throws Exception { checkError( ""java/android/resources"", ""r"", ""'java/android/resources/res/somefile.xml' is not in the expected resource directory "" + ""structure of /{"" + Joiner.on(',').join(AndroidResources.RESOURCE_DIRECTORY_TYPES) + ""}"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/somefile.xml', 'r/t/f/m/raw/fold']"", "" )""); } @Test public void testResourcesDoesNotMatchDirectoryLayout_badDirectory_localResources() throws Exception { checkError( ""java/android/resources"", ""r"", ""'java/android/resources/res/other/somefile.xml' is not in the expected resource directory "" + ""structure of /{"" + Joiner.on(',').join(AndroidResources.RESOURCE_DIRECTORY_TYPES) + ""}"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/other/somefile.xml', 'r/t/f/m/raw/fold']"", "" )""); } @Test public void testResourcesNotUnderCommonDirectoryFails_localResources() throws Exception { checkError( ""java/android/resources"", ""r"", ""'java/android/resources/r/t/f/m/raw/fold' (generated by '//java/android/resources:r/t/f/m/"" + ""raw/fold') is not in the same directory 'res' "" + ""(derived from java/android/resources/res/raw/speed). "" + ""All resources must share a common directory"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/raw/speed', 'r/t/f/m/raw/fold']"", "" )""); } @Test public void testAssetsAndNoAssetsDirFails_localResources() throws Exception { scratch.file( ""java/android/resources/assets/values/strings.xml"", ""Hello Android!""); checkError( ""java/android/resources"", ""r"", ""'assets' and 'assets_dir' should be either both empty or both non-empty"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" assets = glob(['assets/**']),"", "" )""); } @Test public void testAssetsDirAndNoAssetsFails_localResources() throws Exception { checkError( ""java/cpp/android"", ""r"", ""'assets' and 'assets_dir' should be either both empty or both non-empty"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" assets_dir = 'assets',"", "" )""); } @Test public void testAssetsNotUnderAssetsDirFails_localResources() throws Exception { checkError( ""java/android/resources"", ""r"", ""'java/android/resources/r/t/f/m' (generated by '//java/android/resources:r/t/f/m') "" + ""is not beneath 'assets'"", ""android_binary(name = 'r',"", "" manifest = 'AndroidManifest.xml',"", "" assets_dir = 'assets',"", "" assets = ['assets/valuable', 'r/t/f/m']"", "" )""); } @Test public void testFileLocation_localResources() throws Exception { scratch.file( ""java/android/resources/BUILD"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" )""); ConfiguredTargetAndData r = getConfiguredTargetAndData(""//java/android/resources:r""); assertThat( getFirstArtifactEndingWith(getFilesToBuild(r.getConfiguredTarget()), "".apk"").getRoot()) .isEqualTo(r.getConfiguration().getBinDirectory(RepositoryName.MAIN)); } @Test public void testCustomPackage_localResources() throws Exception { scratch.file( ""a/r/BUILD"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" custom_package = 'com.google.android.bar',"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" )""); ConfiguredTarget r = getConfiguredTarget(""//a/r:r""); assertNoEvents(); List args = getGeneratingSpawnActionArgs(getResourceApk(r)); assertContainsSublist(args, ImmutableList.of(""--packageForR"", ""com.google.android.bar"")); } @Test public void testCustomJavacopts() throws Exception { scratch.file(""java/foo/A.java"", ""foo""); scratch.file( ""java/foo/BUILD"", ""android_binary(name = 'a', manifest = 'AndroidManifest.xml', "", "" srcs = ['A.java'], javacopts = ['-g:lines,source'])""); Artifact deployJar = getFileConfiguredTarget(""//java/foo:a_deploy.jar"").getArtifact(); Action deployAction = getGeneratingAction(deployJar); JavaCompileAction javacAction = (JavaCompileAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(deployAction.getInputs()), ""liba.jar""); assertThat(getJavacArguments(javacAction)).contains(""-g:lines,source""); } @Test public void testFixDepsToolFlag() throws Exception { useConfiguration(""--experimental_fix_deps_tool=autofixer""); scratch.file(""java/foo/A.java"", ""foo""); scratch.file( ""java/foo/BUILD"", ""android_binary(name = 'a', manifest = 'AndroidManifest.xml', "", "" srcs = ['A.java'])""); Iterable commandLine = getJavacArguments( ((JavaCompileAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil() .artifactClosureOf( getGeneratingAction( getFileConfiguredTarget(""//java/foo:a_deploy.jar"") .getArtifact()) .getInputs()), ""liba.jar""))); assertThat(commandLine).containsAtLeast(""--experimental_fix_deps_tool"", ""autofixer"").inOrder(); } @Test public void testFixDepsToolFlagEmpty() throws Exception { scratch.file(""java/foo/A.java"", ""foo""); scratch.file( ""java/foo/BUILD"", ""android_binary(name = 'a', manifest = 'AndroidManifest.xml', "", "" srcs = ['A.java'])""); Iterable commandLine = getJavacArguments( ((JavaCompileAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil() .artifactClosureOf( getGeneratingAction( getFileConfiguredTarget(""//java/foo:a_deploy.jar"") .getArtifact()) .getInputs()), ""liba.jar""))); assertThat(commandLine).containsAtLeast(""--experimental_fix_deps_tool"", ""add_dep"").inOrder(); } @Test public void testAndroidBinaryExportsJavaCompilationArgsProvider() throws Exception { scratch.file(""java/foo/A.java"", ""foo""); scratch.file( ""java/foo/BUILD"", ""android_binary(name = 'a', manifest = 'AndroidManifest.xml', "", "" srcs = ['A.java'], javacopts = ['-g:lines,source'])""); final JavaCompilationArgsProvider provider = JavaInfo.getProvider( JavaCompilationArgsProvider.class, getConfiguredTarget(""//java/foo:a"")); assertThat(provider).isNotNull(); } @Test public void testNoApplicationId_localResources() throws Exception { scratch.file( ""java/a/r/BUILD"", ""android_binary(name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" )""); ConfiguredTarget r = getConfiguredTarget(""//java/a/r:r""); assertNoEvents(); List args = getGeneratingSpawnActionArgs(getResourceApk(r)); Truth.assertThat(args).doesNotContain(""--applicationId""); } @Test public void testDisallowPrecompiledJars() throws Exception { checkError( ""java/precompiled"", ""binary"", // messages: ""does not produce any android_binary srcs files (expected .java or .srcjar)"", // build file: ""android_binary(name = 'binary',"", "" manifest='AndroidManifest.xml',"", "" srcs = [':jar'])"", ""filegroup(name = 'jar',"", "" srcs = ['lib.jar'])""); } @Test public void testDesugarJava8Libs_noProguard() throws Exception { useConfiguration(""--experimental_desugar_java8_libs""); scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "")""); ConfiguredTarget top = getConfiguredTarget(""//java/com/google/android:foo""); Artifact artifact = getBinArtifact(""_dx/foo/_final_classes.dex.zip"", top); assertWithMessage(""_final_classes.dex.zip"").that(artifact).isNotNull(); Action generatingAction = getGeneratingAction(artifact); assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) .containsAtLeast(""classes.dex.zip"", /*canned*/ ""java8_legacy.dex.zip""); } @Test public void testDesugarJava8Libs_withProguard() throws Exception { useConfiguration(""--experimental_desugar_java8_libs""); scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "" proguard_specs = ['foo.cfg'],"", "")""); ConfiguredTarget top = getConfiguredTarget(""//java/com/google/android:foo""); Artifact artifact = getBinArtifact(""_dx/foo/_final_classes.dex.zip"", top); assertWithMessage(""_final_classes.dex.zip"").that(artifact).isNotNull(); Action generatingAction = getGeneratingAction(artifact); assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) .containsAtLeast(""classes.dex.zip"", /*built*/ ""_java8_legacy.dex.zip""); } private List generatingActionArgs(Artifact artifact) throws Exception { Action action = getGeneratingAction(artifact); return ((SpawnAction) action).getArguments(); } @Test public void testDesugarJava8Libs_withProguardWithMap() throws Exception { useConfiguration(""--experimental_desugar_java8_libs""); scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "" proguard_specs = ['foo.cfg'],"", "" proguard_generate_mapping = 1,"", "")""); ConfiguredTarget top = getConfiguredTarget(""//java/com/google/android:foo""); Artifact desugaredLibraryDex = getBinArtifact(""_dx/foo/_java8_legacy.dex.zip"", top); List desugaredLibraryActionArgs = generatingActionArgs(desugaredLibraryDex); assertThat(desugaredLibraryActionArgs.get(0)).contains(""build_java8_legacy_dex""); Artifact desugaredLibraryDexProguardMap = getBinArtifact(""_dx/foo/_java8_legacy.dex.map"", top); MoreAsserts.assertContainsSublist( desugaredLibraryActionArgs, ""--rules"", getBinArtifact(""_dx/foo/_java8_legacy.dex.pgcfg"", top).getExecPathString()); MoreAsserts.assertContainsSublist( desugaredLibraryActionArgs, ""--output"", desugaredLibraryDex.getExecPathString()); MoreAsserts.assertContainsSublist( desugaredLibraryActionArgs, ""--output_map"", desugaredLibraryDexProguardMap.getExecPathString()); Artifact proguardMap = getBinArtifact(""foo_proguard.map"", top); List mergeProguardMapsActionArgs = ((SpawnAction) getGeneratingAction(proguardMap)).getArguments(); assertThat(mergeProguardMapsActionArgs.get(0)).contains(""merge_proguard_maps""); assertThat(flagValues(""--pg-map"", mergeProguardMapsActionArgs)) .isEqualTo( ImmutableSet.of( desugaredLibraryDexProguardMap.getExecPathString(), getBinArtifact(""_dx/foo/_proguard_output_for_desugared_library.map"", top) .getExecPathString())); assertThat(flagValue(""--pg-map-output"", mergeProguardMapsActionArgs)) .isEqualTo(proguardMap.getExecPathString()); Artifact artifact = getBinArtifact(""_dx/foo/_final_classes.dex.zip"", top); assertWithMessage(""_final_classes.dex.zip"").that(artifact).isNotNull(); Action generatingAction = getGeneratingAction(artifact); assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) .containsAtLeast(""classes.dex.zip"", /*built*/ ""_java8_legacy.dex.zip""); } @Test public void testDesugarJava8Libs_withProguardWithoutSpecsWithMap() throws Exception { useConfiguration(""--experimental_desugar_java8_libs""); scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" multidex = 'native',"", "" proguard_generate_mapping = 1,"", "")""); ConfiguredTarget top = getConfiguredTarget(""//java/com/google/android:foo""); Artifact desugaredLibraryDex = getBinArtifact(""_dx/foo/_java8_legacy.dex.zip"", top); assertThat(getGeneratingAction(desugaredLibraryDex)).isNull(); Artifact proguardMap = getBinArtifact(""foo_proguard.map"", top); assertThat(getGeneratingAction(proguardMap)).isInstanceOf(FailAction.class); Artifact artifact = getBinArtifact(""_dx/foo/_final_classes.dex.zip"", top); assertWithMessage(""_final_classes.dex.zip"").that(artifact).isNotNull(); Action generatingAction = getGeneratingAction(artifact); assertThat(ActionsTestUtil.baseArtifactNames(generatingAction.getInputs())) .containsAtLeast(""classes.dex.zip"", /*canned*/ ""java8_legacy.dex.zip""); } @Test public void testApplyProguardMapping() throws Exception { scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" proguard_apply_mapping = 'proguard.map',"", "" proguard_specs = ['foo.pro'],"", "" manifest = 'AndroidManifest.xml',"", "")""); ConfiguredTarget ct = getConfiguredTarget(""//java/com/google/android:foo""); Artifact artifact = artifactByPath(getFilesToBuild(ct), ""_proguard.jar""); Action generatingAction = getGeneratingAction(artifact); assertThat(Artifact.asExecPaths(generatingAction.getInputs())) .contains(""java/com/google/android/proguard.map""); // Cannot use assertThat().containsAllOf().inOrder() as that does not assert that the elements // are consecutive. MoreAsserts.assertContainsSublist( getGeneratingSpawnActionArgs(artifact), ""-applymapping"", ""java/com/google/android/proguard.map""); } @Test public void testApplyProguardMappingWithNoSpec() throws Exception { checkError( ""java/com/google/android"", ""foo"", // messages: ""'proguard_apply_mapping' can only be used when 'proguard_specs' is also set"", // build file: ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" proguard_apply_mapping = 'proguard.map',"", "" manifest = 'AndroidManifest.xml',"", "")""); } @Test public void testApplyProguardDictionary() throws Exception { scratch.file( ""java/com/google/android/BUILD"", ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" proguard_apply_dictionary = 'dictionary.txt',"", "" proguard_specs = ['foo.pro'],"", "" manifest = 'AndroidManifest.xml',"", "")""); ConfiguredTarget ct = getConfiguredTarget(""//java/com/google/android:foo""); Artifact artifact = artifactByPath(getFilesToBuild(ct), ""_proguard.jar""); Action generatingAction = getGeneratingAction(artifact); assertThat(Artifact.asExecPaths(generatingAction.getInputs())) .contains(""java/com/google/android/dictionary.txt""); // Cannot use assertThat().containsAllOf().inOrder() as that does not assert that the elements // are consecutive. MoreAsserts.assertContainsSublist( getGeneratingSpawnActionArgs(artifact), ""-obfuscationdictionary"", ""java/com/google/android/dictionary.txt""); MoreAsserts.assertContainsSublist( getGeneratingSpawnActionArgs(artifact), ""-classobfuscationdictionary"", ""java/com/google/android/dictionary.txt""); MoreAsserts.assertContainsSublist( getGeneratingSpawnActionArgs(artifact), ""-packageobfuscationdictionary"", ""java/com/google/android/dictionary.txt""); } @Test public void testApplyProguardDictionaryWithNoSpec() throws Exception { checkError( ""java/com/google/android"", ""foo"", // messages: ""'proguard_apply_dictionary' can only be used when 'proguard_specs' is also set"", // build file: ""android_binary("", "" name = 'foo',"", "" srcs = ['foo.java'],"", "" proguard_apply_dictionary = 'dictionary.txt',"", "" manifest = 'AndroidManifest.xml',"", "")""); } @Test public void testFeatureFlagsAttributeSetsSelectInDependency() throws Exception { useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.file( ""java/com/foo/BUILD"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_setting("", "" name = 'flag1@on',"", "" flag_values = {':flag1': 'on'},"", "" transitive_configs = [':flag1'],"", "")"", ""config_feature_flag("", "" name = 'flag2',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_setting("", "" name = 'flag2@on',"", "" flag_values = {':flag2': 'on'},"", "" transitive_configs = [':flag2'],"", "")"", ""android_library("", "" name = 'lib',"", "" srcs = select({"", "" ':flag1@on': ['Flag1On.java'],"", "" '//conditions:default': ['Flag1Off.java'],"", "" }) + select({"", "" ':flag2@on': ['Flag2On.java'],"", "" '//conditions:default': ['Flag2Off.java'],"", "" }),"", "" transitive_configs = [':flag1', ':flag2'],"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':lib'],"", "" feature_flags = {"", "" 'flag1': 'on',"", "" },"", "" transitive_configs = [':flag1', ':flag2'],"", "")""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/foo""); List inputs = prettyArtifactNames(actionsTestUtil().artifactClosureOf(getFinalUnsignedApk(binary))); assertThat(inputs).containsAtLeast(""java/com/foo/Flag1On.java"", ""java/com/foo/Flag2Off.java""); assertThat(inputs).containsNoneOf(""java/com/foo/Flag1Off.java"", ""java/com/foo/Flag2On.java""); } @Test public void testFeatureFlagsAttributeSetsSelectInBinary() throws Exception { useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.file( ""java/com/foo/BUILD"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_setting("", "" name = 'flag1@on',"", "" flag_values = {':flag1': 'on'},"", "" transitive_configs = [':flag1'],"", "")"", ""config_feature_flag("", "" name = 'flag2',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_setting("", "" name = 'flag2@on',"", "" flag_values = {':flag2': 'on'},"", "" transitive_configs = [':flag2'],"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = select({"", "" ':flag1@on': ['Flag1On.java'],"", "" '//conditions:default': ['Flag1Off.java'],"", "" }) + select({"", "" ':flag2@on': ['Flag2On.java'],"", "" '//conditions:default': ['Flag2Off.java'],"", "" }),"", "" feature_flags = {"", "" 'flag1': 'on',"", "" },"", "" transitive_configs = [':flag1', ':flag2'],"", "")""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/foo""); List inputs = prettyArtifactNames(actionsTestUtil().artifactClosureOf(getFinalUnsignedApk(binary))); assertThat(inputs).containsAtLeast(""java/com/foo/Flag1On.java"", ""java/com/foo/Flag2Off.java""); assertThat(inputs).containsNoneOf(""java/com/foo/Flag1Off.java"", ""java/com/foo/Flag2On.java""); } @Test public void testFeatureFlagsAttributeSetsSelectInBinaryAlias() throws Exception { useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.file( ""java/com/foo/BUILD"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_setting("", "" name = 'flag1@on',"", "" flag_values = {':flag1': 'on'},"", "" transitive_configs = [':flag1'],"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = select({"", "" ':flag1@on': ['Flag1On.java'],"", "" '//conditions:default': ['Flag1Off.java'],"", "" }),"", "" feature_flags = {"", "" 'flag1': 'on',"", "" },"", "" transitive_configs = [':flag1'],"", "")"", ""alias("", "" name = 'alias',"", "" actual = ':foo',"", "")""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/foo:alias""); List inputs = prettyArtifactNames(actionsTestUtil().artifactClosureOf(getFinalUnsignedApk(binary))); assertThat(inputs).contains(""java/com/foo/Flag1On.java""); assertThat(inputs).doesNotContain(""java/com/foo/Flag1Off.java""); } @Test public void testFeatureFlagsAttributeFailsAnalysisIfFlagValueIsInvalid() throws Exception { reporter.removeHandler(failFastHandler); useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.file( ""java/com/foo/BUILD"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_setting("", "" name = 'flag1@on',"", "" flag_values = {':flag1': 'on'},"", "" transitive_configs = [':flag1'],"", "")"", ""android_library("", "" name = 'lib',"", "" srcs = select({"", "" ':flag1@on': ['Flag1On.java'],"", "" '//conditions:default': ['Flag1Off.java'],"", "" }),"", "" transitive_configs = [':flag1'],"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" deps = [':lib'],"", "" feature_flags = {"", "" 'flag1': 'invalid',"", "" },"", "" transitive_configs = [':flag1'],"", "")""); assertThat(getConfiguredTarget(""//java/com/foo"")).isNull(); assertContainsEvent( ""in config_feature_flag rule //java/com/foo:flag1: "" + ""value must be one of [\""off\"", \""on\""], but was \""invalid\""""); } @Test public void testFeatureFlagsAttributeFailsAnalysisIfFlagValueIsInvalidEvenIfNotUsed() throws Exception { reporter.removeHandler(failFastHandler); useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.file( ""java/com/foo/BUILD"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_setting("", "" name = 'flag1@on',"", "" flag_values = {':flag1': 'on'},"", "" transitive_configs = [':flag1'],"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" feature_flags = {"", "" 'flag1': 'invalid',"", "" },"", "" transitive_configs = [':flag1'],"", "")""); assertThat(getConfiguredTarget(""//java/com/foo"")).isNull(); assertContainsEvent( ""in config_feature_flag rule //java/com/foo:flag1: "" + ""value must be one of [\""off\"", \""on\""], but was \""invalid\""""); } @Test public void testFeatureFlagsAttributeFailsAnalysisIfFlagIsAliased() throws Exception { reporter.removeHandler(failFastHandler); useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.file( ""java/com/foo/BUILD"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""alias("", "" name = 'alias',"", "" actual = 'flag1',"", "" transitive_configs = [':flag1'],"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" feature_flags = {"", "" 'alias': 'on',"", "" },"", "" transitive_configs = [':flag1'],"", "")""); assertThat(getConfiguredTarget(""//java/com/foo"")).isNull(); assertContainsEvent( ""in feature_flags attribute of android_binary rule //java/com/foo:foo: "" + ""Feature flags must be named directly, not through aliases; "" + ""use '//java/com/foo:flag1', not '//java/com/foo:alias'""); } @Test public void testFeatureFlagsAttributeSetsFeatureFlagProviderValues() throws Exception { useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.file( ""java/com/foo/reader.bzl"", ""def _impl(ctx):"", "" ctx.actions.write("", "" ctx.outputs.java,"", "" '\\n'.join(["", "" str(target.label) + ': ' + target[config_common.FeatureFlagInfo].value"", "" for target in ctx.attr.flags]))"", "" return [DefaultInfo(files=depset([ctx.outputs.java]))]"", ""flag_reader = rule("", "" implementation=_impl,"", "" attrs={'flags': attr.label_list(providers=[config_common.FeatureFlagInfo])},"", "" outputs={'java': '%{name}.java'},"", "")""); scratch.file( ""java/com/foo/BUILD"", ""load('//java/com/foo:reader.bzl', 'flag_reader')"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_feature_flag("", "" name = 'flag2',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""flag_reader("", "" name = 'FooFlags',"", "" flags = [':flag1', ':flag2'],"", "" transitive_configs = [':flag1', ':flag2'],"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = [':FooFlags.java'],"", "" feature_flags = {"", "" 'flag1': 'on',"", "" },"", "" transitive_configs = [':flag1', ':flag2'],"", "")""); Artifact flagList = getFirstArtifactEndingWith( actionsTestUtil() .artifactClosureOf(getFinalUnsignedApk(getConfiguredTarget(""//java/com/foo""))), ""/FooFlags.java""); FileWriteAction action = (FileWriteAction) getGeneratingAction(flagList); assertThat(action.getFileContents()) .isEqualTo(""@//java/com/foo:flag1: on\n@//java/com/foo:flag2: off""); } @Test public void featureFlagsSetByAndroidBinaryAreInRequiredFragments() throws Exception { useConfiguration(""--include_config_fragments_provider=direct""); scratch.file( ""java/com/foo/BUILD"", ""config_feature_flag("", "" name = 'flag1',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = [':FooFlags.java'],"", "" feature_flags = {"", "" 'flag1': 'on',"", "" },"", "")""); ConfiguredTarget ct = getConfiguredTarget(""//java/com/foo:foo""); assertThat(ct.getProvider(RequiredConfigFragmentsProvider.class).getStarlarkOptions()) .containsExactly(Label.parseCanonicalUnchecked(""//java/com/foo:flag1"")); } @Test public void testNocompressExtensions() throws Exception { scratch.file( ""java/r/android/BUILD"", ""android_binary("", "" name = 'r',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/raw/foo.apk'],"", "" nocompress_extensions = ['.apk', '.so'],"", "")""); ConfiguredTarget binary = getConfiguredTarget(""//java/r/android:r""); ValidatedAndroidResources resource = getValidatedResources(binary); List args = resourceArguments(resource); Artifact inputManifest = getFirstArtifactEndingWith( getGeneratingSpawnAction(resource.getManifest()).getInputs(), ""AndroidManifest.xml""); assertContainsSublist( args, ImmutableList.of( ""--primaryData"", ""java/r/android/res::"" + inputManifest.getExecPathString())); assertThat(args).contains(""--uncompressedExtensions""); assertThat(args.get(args.indexOf(""--uncompressedExtensions"") + 1)).isEqualTo("".apk,.so""); assertThat(getGeneratingSpawnActionArgs(getCompressedUnsignedApk(binary))) .containsAtLeast(""--nocompress_suffixes"", "".apk"", "".so"") .inOrder(); assertThat(getGeneratingSpawnActionArgs(getFinalUnsignedApk(binary))) .containsAtLeast(""--nocompress_suffixes"", "".apk"", "".so"") .inOrder(); } @Test public void testFeatureFlagPolicyMustContainRuleToUseFeatureFlags() throws Exception { reporter.removeHandler(failFastHandler); // expecting an error useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.overwriteFile( ""tools/allowlists/config_feature_flag/BUILD"", ""package_group("", "" name = 'config_feature_flag',"", "" packages = ['//flag'])""); scratch.file( ""flag/BUILD"", ""config_feature_flag("", "" name = 'flag',"", "" allowed_values = ['right', 'wrong'],"", "" default_value = 'right',"", "" visibility = ['//java/com/google/android/foo:__pkg__'],"", "")""); scratch.file( ""java/com/google/android/foo/BUILD"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = [':FooFlags.java'],"", "" feature_flags = {"", "" '//flag:flag': 'right',"", "" },"", "" transitive_configs = ['//flag:flag'],"", "")""); assertThat(getConfiguredTarget(""//java/com/google/android/foo:foo"")).isNull(); assertContainsEvent( ""in android_binary rule //java/com/google/android/foo:foo: "" + ""the attribute feature_flags is not available in this package""); } @Test public void testFeatureFlagPolicyDoesNotBlockRuleIfInPolicy() throws Exception { useConfiguration(""--enforce_transitive_configs_for_config_feature_flag""); scratch.overwriteFile( ""tools/allowlists/config_feature_flag/BUILD"", ""package_group("", "" name = 'config_feature_flag',"", "" packages = ['//flag', '//java/com/google/android/foo'])""); scratch.file( ""flag/BUILD"", ""config_feature_flag("", "" name = 'flag',"", "" allowed_values = ['right', 'wrong'],"", "" default_value = 'right',"", "" visibility = ['//java/com/google/android/foo:__pkg__'],"", "")""); scratch.file( ""java/com/google/android/foo/BUILD"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = [':FooFlags.java'],"", "" feature_flags = {"", "" '//flag:flag': 'right',"", "" },"", "" transitive_configs = ['//flag:flag'],"", "")""); assertThat(getConfiguredTarget(""//java/com/google/android/foo:foo"")).isNotNull(); assertNoEvents(); } @Test public void testFeatureFlagPolicyIsNotUsedIfFlagValuesNotUsed() throws Exception { scratch.overwriteFile( ""tools/allowlists/config_feature_flag/BUILD"", ""package_group("", "" name = 'config_feature_flag',"", "" packages = ['*super* busted package group'])""); scratch.file( ""java/com/google/android/foo/BUILD"", ""android_binary("", "" name = 'foo',"", "" manifest = 'AndroidManifest.xml',"", "" srcs = [':FooFlags.java'],"", "")""); assertThat(getConfiguredTarget(""//java/com/google/android/foo:foo"")).isNotNull(); // the package_group is busted, so we would have failed to get this far if we depended on it assertNoEvents(); // Check time: does this test actually test what we're testing for? reporter.removeHandler(failFastHandler); assertThat(getConfiguredTarget(""//tools/allowlists/config_feature_flag:config_feature_flag"")) .isNull(); assertContainsEvent(""*super* busted package group""); } @Test public void testAapt2WithAndroidSdk() throws Exception { scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [ 'res/values/values.xml' ], "", "")""); ConfiguredTarget a = getConfiguredTarget(""//java/a:a""); Artifact apk = getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_RESOURCES_APK); assertThat(getGeneratingSpawnActionArgs(apk)) .containsAtLeast( ""--aapt2"", // The path to aapt2 is different between Blaze and Bazel, so we omit it here. // It's safe to do so as we've already checked for the `--aapt2` flag. ""--tool"", ""AAPT2_PACKAGE""); } @Test public void testAapt2WithAndroidSdkAndDependencies() throws Exception { scratch.file( ""java/b/BUILD"", ""android_library("", "" name = 'b',"", "" srcs = ['B.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [ 'res/values/values.xml' ], "", "")""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" deps = [ '//java/b:b' ],"", "" resource_files = [ 'res/values/values.xml' ], "", "")""); ConfiguredTarget a = getConfiguredTarget(""//java/a:a""); ConfiguredTarget b = getDirectPrerequisite(a, ""//java/b:b""); Artifact classJar = getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR); Artifact apk = getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_RESOURCES_APK); SpawnAction apkAction = getGeneratingSpawnAction(apk); assertThat(getGeneratingSpawnActionArgs(apk)) .containsAtLeast( ""--aapt2"", // The path to aapt2 is different between Blaze and Bazel, so we omit it here. // It's safe to do so as we've already checked for the `--aapt2` flag. ""--tool"", ""AAPT2_PACKAGE""); assertThat(apkAction.getInputs().toList()) .contains(getImplicitOutputArtifact(b, AndroidRuleClasses.ANDROID_COMPILED_SYMBOLS)); SpawnAction classAction = getGeneratingSpawnAction(classJar); assertThat(classAction.getInputs().toList()) .containsAtLeast( getImplicitOutputArtifact(a, AndroidRuleClasses.ANDROID_R_TXT), getImplicitOutputArtifact(b, AndroidRuleClasses.ANDROID_R_TXT)); } @Test public void testAapt2ResourceShrinkingAction() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" shrink_resources = 1,"", "" proguard_specs = ['proguard-spec.pro'],)""); ConfiguredTargetAndData targetAndData = getConfiguredTargetAndData(""//java/com/google/android/hello:hello""); ConfiguredTarget binary = targetAndData.getConfiguredTarget(); Artifact jar = getResourceClassJar(targetAndData); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); assertThat(getGeneratingSpawnActionArgs(jar)).contains(""--finalFields""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); assertThat(artifacts) .containsAtLeast( getFirstArtifactEndingWith(artifacts, ""resource_files.zip""), getFirstArtifactEndingWith(artifacts, ""proguard.jar""), getFirstArtifactEndingWith(artifacts, ""shrunk.ap_"")); List processingArgs = getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, ""resource_files.zip"")); assertThat(flagValue(""--resourcesOutput"", processingArgs)) .endsWith(""hello_files/resource_files.zip""); List proguardArgs = getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, ""proguard.jar"")); assertThat(flagValue(""-outjars"", proguardArgs)).endsWith(""hello_proguard.jar""); List shrinkingArgs = getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, ""shrunk.ap_"")); assertThat(flagValue(""--tool"", shrinkingArgs)).isEqualTo(""SHRINK_AAPT2""); assertThat(flagValue(""--aapt2"", shrinkingArgs)).isEqualTo(flagValue(""--aapt2"", processingArgs)); assertThat(flagValue(""--resources"", shrinkingArgs)) .isEqualTo(flagValue(""--resourcesOutput"", processingArgs)); assertThat(flagValue(""--shrunkJar"", shrinkingArgs)) .isEqualTo(flagValue(""-outjars"", proguardArgs)); assertThat(flagValue(""--proguardMapping"", shrinkingArgs)) .isEqualTo(flagValue(""-printmapping"", proguardArgs)); assertThat(flagValue(""--rTxt"", shrinkingArgs)) .isEqualTo(flagValue(""--rOutput"", processingArgs)); assertThat(flagValue(""--resourcesConfigOutput"", shrinkingArgs)) .endsWith(""resource_optimization.cfg""); List packageArgs = getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, ""_hello_proguard.cfg"")); assertThat(flagValue(""--tool"", packageArgs)).isEqualTo(""AAPT2_PACKAGE""); assertThat(packageArgs).doesNotContain(""--conditionalKeepRules""); } @Test public void testAapt2ResourceShrinking_proguardSpecsAbsent_noShrunkApk() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" shrink_resources = 1,)""); ConfiguredTargetAndData targetAndData = getConfiguredTargetAndData(""//java/com/google/android/hello:hello""); ConfiguredTarget binary = targetAndData.getConfiguredTarget(); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); assertThat(getFirstArtifactEndingWith(artifacts, ""shrunk.ap_"")).isNull(); } @Test public void testAapt2ResourceCycleShrinking() throws Exception { useConfiguration(""--experimental_android_resource_cycle_shrinking=true""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" shrink_resources = 1,"", "" proguard_specs = ['proguard-spec.pro'],)""); ConfiguredTargetAndData targetAndData = getConfiguredTargetAndData(""//java/com/google/android/hello:hello""); ConfiguredTarget binary = targetAndData.getConfiguredTarget(); Artifact jar = getResourceClassJar(targetAndData); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); assertThat(getGeneratingSpawnActionArgs(jar)).contains(""--nofinalFields""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); List packageArgs = getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, ""_hello_proguard.cfg"")); assertThat(flagValue(""--tool"", packageArgs)).isEqualTo(""AAPT2_PACKAGE""); assertThat(packageArgs).contains(""--conditionalKeepRules""); } @Test public void testAapt2ResourceCycleShinkingWithoutResourceShrinking() throws Exception { useConfiguration(""--experimental_android_resource_cycle_shrinking=true""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [ 'res/values/values.xml' ], "", "" shrink_resources = 0,"", "" proguard_specs = ['proguard-spec.pro'],"", "")""); ConfiguredTargetAndData targetAndData = getConfiguredTargetAndData(""//java/com/google/android/hello:a""); ConfiguredTarget binary = targetAndData.getConfiguredTarget(); Artifact jar = getResourceClassJar(targetAndData); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); assertThat(getGeneratingSpawnActionArgs(jar)).doesNotContain(""--nofinalFields""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)); List packageArgs = getGeneratingSpawnActionArgs(getFirstArtifactEndingWith(artifacts, ""_a_proguard.cfg"")); assertThat(flagValue(""--tool"", packageArgs)).isEqualTo(""AAPT2_PACKAGE""); assertThat(packageArgs).doesNotContain(""--conditionalKeepRules""); } @Test public void testAapt2ResourceCycleShrinkingDisabledNoProguardSpecs() throws Exception { useConfiguration(""--experimental_android_resource_cycle_shrinking=true""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" shrink_resources = 1)""); ConfiguredTargetAndData targetAndData = getConfiguredTargetAndData(""//java/com/google/android/hello:hello""); Artifact jar = getResourceClassJar(targetAndData); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); // Final fields should still be generated for non-ProGuarded builds. assertThat(getGeneratingSpawnActionArgs(jar)).contains(""--finalFields""); } @Test public void testAapt2ResourceCycleShrinkingDisabledNoProguardSpecsApplicationResources() throws Exception { useConfiguration(""--experimental_android_resource_cycle_shrinking=true""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'hello',"", "" srcs = ['Foo.java'],"", "" manifest = 'AndroidManifest.xml',"", "" shrink_resources = 1)""); ConfiguredTargetAndData targetAndData = getConfiguredTargetAndData(""//java/com/google/android/hello:hello""); Artifact jar = getResourceClassJar(targetAndData); assertThat(getGeneratingAction(jar).getMnemonic()).isEqualTo(""RClassGenerator""); // Final fields should still be generated for non-ProGuarded builds. assertThat(getGeneratingSpawnActionArgs(jar)).contains(""--finalFields""); } @Test public void testOnlyProguardSpecs() throws Exception { useConfiguration( ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l2',"", "" srcs = ['MoreMaps.java'],"", "" neverlink = 1)"", ""android_library(name = 'l3',"", "" idl_srcs = ['A.aidl'],"", "" deps = [':l2'])"", ""android_library(name = 'l4',"", "" srcs = ['SubMoreMaps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l3', ':l4'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); ConfiguredTargetAndData binary = getConfiguredTargetAndData(""//java/com/google/android/hello:b""); checkProguardUse( binary.getConfiguredTarget(), ""b_proguard.jar"", false, /*passes=*/ null, // no-op since passes is null. /*bytecodeOptimizationPassActions=*/ 0, binary.getConfiguration().getBinFragment(RepositoryName.MAIN) + ""/java/com/google/android/hello/proguard/b/legacy_b_combined_library_jars.jar""); } @Test public void testOnlyProguardSpecsProguardJar() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l2',"", "" srcs = ['MoreMaps.java'],"", "" neverlink = 1)"", ""android_library(name = 'l3',"", "" idl_srcs = ['A.aidl'],"", "" deps = [':l2'])"", ""android_library(name = 'l4',"", "" srcs = ['SubMoreMaps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l3', ':l4'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_generate_mapping = 1,"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); ConfiguredTarget output = getConfiguredTarget(""//java/com/google/android/hello:b_proguard.jar""); assertProguardUsed(output); output = getConfiguredTarget(""//java/com/google/android/hello:b_proguard.map""); assertWithMessage(""proguard.map is not in the rule output"") .that( actionsTestUtil() .getActionForArtifactEndingWith(getFilesToBuild(output), ""_proguard.map"")) .isNotNull(); } @Test public void testCommandLineForMultipleProguardSpecs() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l1',"", "" srcs = ['Maps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l1'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:b""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith(getFilesToBuild(binary), ""_proguard.jar""); assertWithMessage(""Proguard action does not contain expected inputs."") .that(prettyArtifactNames(action.getInputs())) .containsAtLeast( ""java/com/google/android/hello/proguard-spec.pro"", ""java/com/google/android/hello/proguard-spec1.pro"", ""java/com/google/android/hello/proguard-spec2.pro""); assertThat(action.getArguments()) .containsExactly( getProguardBinary().getExecPathString(), ""-forceprocessing"", ""-injars"", execPathEndingWith(action.getInputs(), ""b_deploy.jar""), ""-outjars"", execPathEndingWith(action.getOutputs(), ""b_proguard.jar""), // Only one combined library jar ""-libraryjars"", execPathEndingWith(action.getInputs(), ""legacy_b_combined_library_jars.jar""), ""@"" + execPathEndingWith(action.getInputs(), ""b_proguard.cfg""), ""@java/com/google/android/hello/proguard-spec.pro"", ""@java/com/google/android/hello/proguard-spec1.pro"", ""@java/com/google/android/hello/proguard-spec2.pro"", ""-printseeds"", execPathEndingWith(action.getOutputs(), ""_proguard.seeds""), ""-printusage"", execPathEndingWith(action.getOutputs(), ""_proguard.usage""), ""-printconfiguration"", execPathEndingWith(action.getOutputs(), ""_proguard.config"")) .inOrder(); } /** Regression test for b/17790639 */ @Test public void testNoDuplicatesInProguardCommand() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l1',"", "" srcs = ['Maps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l1'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:b""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith(getFilesToBuild(binary), ""_proguard.jar""); assertThat(action.getArguments()) .containsExactly( getProguardBinary().getExecPathString(), ""-forceprocessing"", ""-injars"", execPathEndingWith(action.getInputs(), ""b_deploy.jar""), ""-outjars"", execPathEndingWith(action.getOutputs(), ""b_proguard.jar""), // Only one combined library jar ""-libraryjars"", execPathEndingWith(action.getInputs(), ""legacy_b_combined_library_jars.jar""), ""@"" + execPathEndingWith(action.getInputs(), ""b_proguard.cfg""), ""@java/com/google/android/hello/proguard-spec.pro"", ""@java/com/google/android/hello/proguard-spec1.pro"", ""@java/com/google/android/hello/proguard-spec2.pro"", ""-printseeds"", execPathEndingWith(action.getOutputs(), ""_proguard.seeds""), ""-printusage"", execPathEndingWith(action.getOutputs(), ""_proguard.usage""), ""-printconfiguration"", execPathEndingWith(action.getOutputs(), ""_proguard.config"")) .inOrder(); } @Test public void testProguardMapping() throws Exception { useConfiguration( ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro'],"", "" proguard_generate_mapping = 1)""); checkProguardUse( getConfiguredTarget(""//java/com/google/android/hello:b""), ""b_proguard.jar"", true, /*passes=*/ null, // no-op since passes is null. /*bytecodeOptimizationPassActions=*/ 0, getAndroidJarPath()); } @Test public void testProguardMappingProvider() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l2',"", "" srcs = ['MoreMaps.java'],"", "" neverlink = 1)"", ""android_library(name = 'l3',"", "" idl_srcs = ['A.aidl'],"", "" deps = [':l2'])"", ""android_library(name = 'l4',"", "" srcs = ['SubMoreMaps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b1',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l3', ':l4'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_generate_mapping = 1,"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])"", ""android_binary(name = 'b2',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l3', ':l4'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); ConfiguredTarget output = getConfiguredTarget(""//java/com/google/android/hello:b1""); assertProguardUsed(output); Artifact mappingArtifact = getBinArtifact(""b1_proguard.map"", output); ProguardMappingProvider mappingProvider = output.get(ProguardMappingProvider.PROVIDER); assertThat(mappingProvider.getProguardMapping()).isEqualTo(mappingArtifact); output = getConfiguredTarget(""//java/com/google/android/hello:b2""); assertProguardUsed(output); assertThat(output.get(ProguardMappingProvider.PROVIDER)).isNull(); } @Test public void testProguardSpecFromLibraryUsedInBinary() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l2',"", "" srcs = ['MoreMaps.java'],"", "" proguard_specs = ['library_spec.cfg'])"", ""android_library(name = 'l3',"", "" idl_srcs = ['A.aidl'],"", "" proguard_specs = ['library_spec.cfg'],"", "" deps = [':l2'])"", ""android_library(name = 'l4',"", "" srcs = ['SubMoreMaps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l3', ':l4'],"", "" proguard_specs = ['proguard-spec.pro'],"", "" manifest = 'AndroidManifest.xml',)""); assertProguardUsed(getConfiguredTarget(""//java/com/google/android/hello:b"")); assertProguardGenerated(getConfiguredTarget(""//java/com/google/android/hello:b"")); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( getFilesToBuild(getConfiguredTarget(""//java/com/google/android/hello:b"")), ""_proguard.jar""); assertThat(prettyArtifactNames(action.getInputs())) .contains(""java/com/google/android/hello/proguard-spec.pro""); assertThat(prettyArtifactNames(action.getInputs())) .contains( ""java/com/google/android/hello/validated_proguard/l2/java/com/google/android/hello/library_spec.cfg_valid""); assertThat(prettyArtifactNames(action.getInputs())).containsNoDuplicates(); } @Test public void testResourcesUsedInProguardGenerate() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = ['res/values/strings.xml'],"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); scratch.file( ""java/com/google/android/hello/res/values/strings.xml"", ""Hello Android!""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:b""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), ""_proguard.cfg""); assertProguardGenerated(binary); assertWithMessage(""Generate proguard action does not contain expected input."") .that(prettyArtifactNames(action.getInputs())) .contains(""java/com/google/android/hello/res/values/strings.xml""); } @Test public void testUseSingleJarForLibraryJars() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l1',"", "" srcs = ['Maps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l1'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); ConfiguredTargetAndData binary = getConfiguredTargetAndData(""//java/com/google/android/hello:b""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( getFilesToBuild(binary.getConfiguredTarget()), ""_proguard.jar""); checkProguardLibJars( action, binary.getConfiguration().getBinFragment(RepositoryName.MAIN) + ""/java/com/google/android/hello/proguard/b/legacy_b_combined_library_jars.jar""); } @Test public void testUseSingleJarForFilteredLibraryJars() throws Exception { useConfiguration(""--experimental_filter_library_jar_with_program_jar=true""); scratch.file( ""java/com/google/android/hello/BUILD"", ""android_library(name = 'l1',"", "" srcs = ['Maps.java'],"", "" neverlink = 1)"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" deps = [':l1'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro', 'proguard-spec1.pro',"", "" 'proguard-spec2.pro'])""); ConfiguredTargetAndData binary = getConfiguredTargetAndData(""//java/com/google/android/hello:b""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( getFilesToBuild(binary.getConfiguredTarget()), ""_proguard.jar""); checkProguardLibJars( action, binary.getConfiguration().getBinFragment(RepositoryName.MAIN) + ""/java/com/google/android/hello/proguard/b/legacy_b_combined_library_jars_filtered.jar""); } @Test public void testOnlyOneLibraryJar() throws Exception { scratch.file( ""java/com/google/android/hello/BUILD"", ""android_binary(name = 'b',"", "" srcs = ['HelloApp.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard-spec.pro'],"", "" proguard_generate_mapping = 1)""); ConfiguredTarget binary = getConfiguredTarget(""//java/com/google/android/hello:b""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith(getFilesToBuild(binary), ""_proguard.jar""); checkProguardLibJars(action, getAndroidJarPath()); } @Test public void testApkInfoAccessibleFromStarlark() throws Exception { scratch.file( ""java/com/google/android/BUILD"", ""load(':postprocess.bzl', 'postprocess')"", ""android_binary(name = 'b1',"", "" srcs = ['b1.java'],"", "" manifest = 'AndroidManifest.xml')"", ""postprocess(name = 'postprocess', dep = ':b1')""); scratch.file( ""java/com/google/android/postprocess.bzl"", ""def _impl(ctx):"", "" return [DefaultInfo(files=depset("", "" [ctx.attr.dep[ApkInfo].signed_apk, ctx.attr.dep[ApkInfo].deploy_jar]))]"", ""postprocess = rule(implementation=_impl,"", "" attrs={'dep': attr.label(providers=[ApkInfo])})""); ConfiguredTarget postprocess = getConfiguredTarget(""//java/com/google/android:postprocess""); assertThat(postprocess).isNotNull(); assertThat( prettyArtifactNames(postprocess.getProvider(FilesToRunProvider.class).getFilesToRun())) .containsExactly(""java/com/google/android/b1.apk"", ""java/com/google/android/b1_deploy.jar""); } @Test public void testInstrumentationInfoAccessibleFromStarlark() throws Exception { scratch.file( ""java/com/google/android/instr/BUILD"", ""load(':instr.bzl', 'instr')"", ""android_binary(name = 'b1',"", "" srcs = ['b1.java'],"", "" instruments = ':b2',"", "" manifest = 'AndroidManifest.xml')"", ""android_binary(name = 'b2',"", "" srcs = ['b2.java'],"", "" manifest = 'AndroidManifest.xml')"", ""instr(name = 'instr', dep = ':b1')""); scratch.file( ""java/com/google/android/instr/instr.bzl"", ""def _impl(ctx):"", "" target = ctx.attr.dep[AndroidInstrumentationInfo].target.signed_apk"", "" return [DefaultInfo(files=depset([target]))]"", ""instr = rule(implementation=_impl,"", "" attrs={'dep': attr.label(providers=[AndroidInstrumentationInfo])})""); ConfiguredTarget instr = getConfiguredTarget(""//java/com/google/android/instr""); assertThat(instr).isNotNull(); assertThat(prettyArtifactNames(instr.getProvider(FilesToRunProvider.class).getFilesToRun())) .containsExactly(""java/com/google/android/instr/b2.apk""); } @Test public void testInstrumentationInfoCreatableFromStarlark() throws Exception { scratch.file( ""java/com/google/android/instr/BUILD"", ""load(':instr.bzl', 'instr')"", ""android_binary(name = 'b1',"", "" srcs = ['b1.java'],"", "" instruments = ':b2',"", "" manifest = 'AndroidManifest.xml')"", ""android_binary(name = 'b2',"", "" srcs = ['b2.java'],"", "" manifest = 'AndroidManifest.xml')"", ""instr(name = 'instr', dep = ':b1')""); scratch.file( ""java/com/google/android/instr/instr.bzl"", ""def _impl(ctx):"", "" target = ctx.attr.dep[AndroidInstrumentationInfo].target"", "" return [AndroidInstrumentationInfo(target=target)]"", ""instr = rule(implementation=_impl,"", "" attrs={'dep': attr.label(providers=[AndroidInstrumentationInfo])})""); ConfiguredTarget instr = getConfiguredTarget(""//java/com/google/android/instr""); assertThat(instr).isNotNull(); assertThat(instr.get(AndroidInstrumentationInfo.PROVIDER).getTarget().getApk().prettyPrint()) .isEqualTo(""java/com/google/android/instr/b2.apk""); } @Test public void testInstrumentationInfoProviderHasApks() throws Exception { scratch.file( ""java/com/google/android/instr/BUILD"", ""android_binary(name = 'b1',"", "" srcs = ['b1.java'],"", "" instruments = ':b2',"", "" manifest = 'AndroidManifest.xml')"", ""android_binary(name = 'b2',"", "" srcs = ['b2.java'],"", "" manifest = 'AndroidManifest.xml')""); ConfiguredTarget b1 = getConfiguredTarget(""//java/com/google/android/instr:b1""); AndroidInstrumentationInfo provider = b1.get(AndroidInstrumentationInfo.PROVIDER); assertThat(provider.getTarget()).isNotNull(); assertThat(provider.getTarget().getApk().prettyPrint()) .isEqualTo(""java/com/google/android/instr/b2.apk""); } @Test public void testNoInstrumentationInfoProviderIfNotInstrumenting() throws Exception { scratch.file( ""java/com/google/android/instr/BUILD"", ""android_binary(name = 'b1',"", "" srcs = ['b1.java'],"", "" manifest = 'AndroidManifest.xml')""); ConfiguredTarget b1 = getConfiguredTarget(""//java/com/google/android/instr:b1""); AndroidInstrumentationInfo provider = b1.get(AndroidInstrumentationInfo.PROVIDER); assertThat(provider).isNull(); } @Test public void testFilterActionWithInstrumentedBinary() throws Exception { scratch.file( ""java/com/google/android/instr/BUILD"", ""android_binary(name = 'b1',"", "" srcs = ['b1.java'],"", "" instruments = ':b2',"", "" manifest = 'AndroidManifest.xml')"", ""android_binary(name = 'b2',"", "" srcs = ['b2.java'],"", "" manifest = 'AndroidManifest.xml')""); ConfiguredTarget b1 = getConfiguredTarget(""//java/com/google/android/instr:b1""); SpawnAction action = (SpawnAction) actionsTestUtil().getActionForArtifactEndingWith(getFilesToBuild(b1), ""_filtered.jar""); assertThat(action.getArguments()) .containsAtLeast( ""--inputZip"", getFirstArtifactEndingWith(action.getInputs(), ""b1_deploy.jar"").getExecPathString(), ""--filterZips"", getFirstArtifactEndingWith(action.getInputs(), ""b2_deploy.jar"").getExecPathString(), ""--outputZip"", getFirstArtifactEndingWith(action.getOutputs(), ""b1_filtered.jar"").getExecPathString(), ""--filterTypes"", "".class"", ""--checkHashMismatch"", ""IGNORE"", ""--explicitFilters"", ""/BR\\.class$,/databinding/[^/]+Binding\\.class$,(^|/)R\\.class,(^|/)R\\$.*\\.class"", ""--outputMode"", ""DONT_CARE""); } /** * 'proguard_specs' attribute gets read by an implicit outputs function: the current heuristic is * that if this attribute is configurable, we assume its contents are non-empty and thus create * the mybinary_proguard.jar output. Test that here. */ @Test public void testConfigurableProguardSpecs() throws Exception { useConfiguration( ""--experimental_use_dex_splitter_for_incremental_dexing=false"", ""--experimental_incremental_dexing_after_proguard_by_default=false"", ""--experimental_incremental_dexing_after_proguard=1""); scratch.file( ""conditions/BUILD"", ""config_setting("", "" name = 'a',"", "" values = {'foo': 'a'})"", ""config_setting("", "" name = 'b',"", "" values = {'foo': 'b'})""); scratchConfiguredTarget( ""java/foo"", ""abin"", ""android_binary("", "" name = 'abin',"", "" srcs = ['a.java'],"", "" proguard_specs = select({"", "" '//conditions:a': [':file1.pro'],"", "" '//conditions:b': [],"", "" '//conditions:default': [':file3.pro'],"", "" }) + ["", // Add a long list here as a regression test for b/68238721 "" 'file4.pro',"", "" 'file5.pro',"", "" 'file6.pro',"", "" 'file7.pro',"", "" 'file8.pro',"", "" ],"", "" manifest = 'AndroidManifest.xml')""); checkProguardUse( getConfiguredTarget(""//java/foo:abin""), ""abin_proguard.jar"", /*expectMapping=*/ false, /*passes=*/ null, // no-op since passes is null. /*bytecodeOptimizationPassActions=*/ 0, getAndroidJarPath()); } @Test public void alwaysSkipParsingActionWithAapt2() throws Exception { scratch.file( ""java/b/BUILD"", ""android_library("", "" name = 'b',"", "" srcs = ['B.java'],"", "" manifest = 'AndroidManifest.xml',"", "" resource_files = [ 'res/values/values.xml' ], "", "")""); scratch.file( ""java/a/BUILD"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" deps = [ '//java/b:b' ],"", "" resource_files = [ 'res/values/values.xml' ], "", "")""); ConfiguredTarget a = getConfiguredTarget(""//java/a:a""); ConfiguredTarget b = getDirectPrerequisite(a, ""//java/b:b""); List resourceProcessingArgs = getGeneratingSpawnActionArgs(getValidatedResources(a).getApk()); assertThat(resourceProcessingArgs).contains(""AAPT2_PACKAGE""); String directData = resourceProcessingArgs.get(resourceProcessingArgs.indexOf(""--directData"") + 1); assertThat(directData).contains(""symbols.zip""); assertThat(directData).doesNotContain(""merged.bin""); List resourceMergingArgs = getGeneratingSpawnActionArgs(getValidatedResources(b).getJavaClassJar()); assertThat(resourceMergingArgs).contains(""MERGE_COMPILED""); } @Test public void starlarkJavaInfoToAndroidBinaryAttributes() throws Exception { scratch.file( ""java/r/android/extension.bzl"", ""def _impl(ctx):"", "" dep_params = ctx.attr.dep[JavaInfo]"", "" return [dep_params]"", ""my_rule = rule("", "" _impl,"", "" attrs = {"", "" 'dep': attr.label(),"", "" },"", "")""); scratch.file( ""java/r/android/BUILD"", ""load(':extension.bzl', 'my_rule')"", ""android_library("", "" name = 'al_bottom_for_deps',"", "" srcs = ['java/A.java'],"", "")"", ""my_rule("", "" name = 'mya',"", "" dep = ':al_bottom_for_deps',"", "")"", ""android_binary("", "" name = 'foo_app',"", "" srcs = ['java/B.java'],"", "" deps = [':mya'],"", "" manifest = 'AndroidManifest.xml',"", // TODO(b/75051107): Remove the following line when fixed. "" incremental_dexing = 0,"", "")""); // Test that all bottom jars are on the runtime classpath of the app. ConfiguredTarget target = getConfiguredTarget(""//java/r/android:foo_app""); ImmutableList transitiveSrcJars = OutputGroupInfo.get(target).getOutputGroup(JavaSemantics.SOURCE_JARS_OUTPUT_GROUP).toList(); assertThat(ActionsTestUtil.baseArtifactNames(transitiveSrcJars)) .containsExactly(""libal_bottom_for_deps-src.jar"", ""libfoo_app-src.jar""); ImmutableList directSrcJar = OutputGroupInfo.get(target) .getOutputGroup(JavaSemantics.DIRECT_SOURCE_JARS_OUTPUT_GROUP) .toList(); assertThat(ActionsTestUtil.baseArtifactNames(directSrcJar)) .containsExactly(""libfoo_app-src.jar""); } @Test public void androidManifestMergerOrderAlphabetical_MergeesSortedByExecPath() throws Exception { useConfiguration(""--android_manifest_merger_order=alphabetical""); /* * Dependency hierarchy: * - //java/binary:application * - //java/binary:library * - //java/common:theme * - //java/android:utility * - //java/common:common * - //java/android:core */ scratch.overwriteFile( ""java/android/BUILD"", ""android_library("", "" name = 'core',"", "" manifest = 'core/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['core/res/values/strings.xml'],"", "")"", ""android_library("", "" name = 'utility',"", "" manifest = 'utility/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['utility/res/values/values.xml'],"", "" deps = ['//java/common:common'],"", "")""); scratch.file( ""java/binary/BUILD"", ""android_binary("", "" name = 'application',"", "" srcs = ['App.java'],"", "" manifest = 'app/AndroidManifest.xml',"", "" deps = [':library'],"", "")"", ""android_library("", "" name = 'library',"", "" manifest = 'library/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" deps = ['//java/common:theme', '//java/android:utility'],"", "")""); scratch.file( ""java/common/BUILD"", ""android_library("", "" name = 'common',"", "" manifest = 'common/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['common/res/values/common.xml'],"", "" deps = ['//java/android:core'],"", "")"", ""android_library("", "" name = 'theme',"", "" manifest = 'theme/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['theme/res/values/values.xml'],"", "")""); // These have to be found via the same inheritance hierarchy, because getDirectPrerequsite can // only traverse one level. ConfiguredTarget application = getConfiguredTarget(""//java/binary:application""); ConfiguredTarget library = getDirectPrerequisite(application, ""//java/binary:library""); ConfiguredTarget theme = getDirectPrerequisite(library, ""//java/common:theme""); ConfiguredTarget utility = getDirectPrerequisite(library, ""//java/android:utility""); ConfiguredTarget common = getDirectPrerequisite(utility, ""//java/common:common""); ConfiguredTarget core = getDirectPrerequisite(common, ""//java/android:core""); Artifact androidCoreManifest = getLibraryManifest(core); Artifact androidUtilityManifest = getLibraryManifest(utility); Artifact binaryLibraryManifest = getLibraryManifest(library); Artifact commonManifest = getLibraryManifest(common); Artifact commonThemeManifest = getLibraryManifest(theme); assertThat(getBinaryMergeeManifests(application)) .containsExactlyEntriesIn( ImmutableMap.of( androidCoreManifest.getExecPath().toString(), ""//java/android:core"", androidUtilityManifest.getExecPath().toString(), ""//java/android:utility"", binaryLibraryManifest.getExecPath().toString(), ""//java/binary:library"", commonManifest.getExecPath().toString(), ""//java/common:common"", commonThemeManifest.getExecPath().toString(), ""//java/common:theme"")) .inOrder(); } @Test public void androidManifestMergerOrderAlphabeticalByConfiguration_MergeesSortedByPathInBinOrGen() throws Exception { useConfiguration( ""--android_manifest_merger_order=alphabetical_by_configuration""); scratch.overwriteFile( ""java/android/BUILD"", ""android_library("", "" name = 'core',"", "" manifest = 'core/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['core/res/values/strings.xml'],"", "")"", ""android_library("", "" name = 'utility',"", "" manifest = 'utility/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['utility/res/values/values.xml'],"", "" deps = ['//java/common:common'],"", "" transitive_configs = ['//flags:a', '//flags:b'],"", "")""); scratch.file( ""java/binary/BUILD"", ""android_binary("", "" name = 'application',"", "" srcs = ['App.java'],"", "" manifest = 'app/AndroidManifest.xml',"", "" deps = [':library'],"", "" feature_flags = {"", "" '//flags:a': 'on',"", "" '//flags:b': 'on',"", "" '//flags:c': 'on',"", "" },"", "" transitive_configs = ['//flags:a', '//flags:b', '//flags:c'],"", "")"", ""android_library("", "" name = 'library',"", "" manifest = 'library/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" deps = ['//java/common:theme', '//java/android:utility'],"", "" transitive_configs = ['//flags:a', '//flags:b', '//flags:c'],"", "")""); scratch.file( ""java/common/BUILD"", ""android_library("", "" name = 'common',"", "" manifest = 'common/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['common/res/values/common.xml'],"", "" deps = ['//java/android:core'],"", "" transitive_configs = ['//flags:a'],"", "")"", ""android_library("", "" name = 'theme',"", "" manifest = 'theme/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['theme/res/values/values.xml'],"", "" transitive_configs = ['//flags:a', '//flags:b', '//flags:c'],"", "")""); scratch.file( ""flags/BUILD"", ""config_feature_flag("", "" name = 'a',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_feature_flag("", "" name = 'b',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")"", ""config_feature_flag("", "" name = 'c',"", "" allowed_values = ['on', 'off'],"", "" default_value = 'off',"", "")""); assertThat(getBinaryMergeeManifests(getConfiguredTarget(""//java/binary:application"")).values()) .containsExactly( ""//java/android:core"", ""//java/android:utility"", ""//java/binary:library"", ""//java/common:common"", ""//java/common:theme"") .inOrder(); } @Test public void testAndroidStarlarkApiNativeLibs() throws Exception { scratch.file( ""java/a/fetch_native_libs.bzl"", ""def _impl(ctx):"", "" libs = ctx.attr.android_binary.android.native_libs"", "" return [DefaultInfo(files = libs.values()[0])]"", ""fetch_native_libs = rule(implementation = _impl,"", "" attrs = {"", "" 'android_binary': attr.label(),"", "" },"", "")""); scratch.file( ""java/a/BUILD"", ""load('//java/a:fetch_native_libs.bzl', 'fetch_native_libs')"", ""android_binary("", "" name = 'app',"", "" srcs=['Main.java'],"", "" manifest='AndroidManifest.xml',"", "" deps=[':cc'],"", "")"", ""cc_library("", "" name = 'cc',"", "" srcs = ['cc.cc'],"", "")"", ""fetch_native_libs("", "" name = 'clibs',"", "" android_binary = 'app',"", "")""); ConfiguredTarget clibs = getConfiguredTarget(""//java/a:clibs""); assertThat( ActionsTestUtil.baseArtifactNames( clibs.getProvider(FileProvider.class).getFilesToBuild())) .containsExactly(""libapp.so""); } @Test public void testInstrumentsManifestMergeEnabled() throws Exception { // This is the incorrect behavior where dependency manifests are merged into the test apk. useConfiguration(""--noexperimental_disable_instrumentation_manifest_merge""); scratch.file( ""java/com/google/android/instr/BUILD"", ""android_binary("", "" name = 'b1',"", "" srcs = ['b1.java'],"", "" instruments = ':b2',"", "" deps = [':lib'],"", "" manifest = 'test/AndroidManifest.xml',"", "")"", ""android_library("", "" name = 'lib',"", "" manifest = 'lib/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['lib/res/values/strings.xml'],"", "")"", ""android_binary("", "" name = 'b2',"", "" srcs = ['b2.java'],"", "" deps = [':lib'],"", "" manifest = 'bin/AndroidManifest.xml',"", "")""); assertThat( getBinaryMergeeManifests(getConfiguredTarget(""//java/com/google/android/instr:b1"")) .values()) .containsExactly(""//java/com/google/android/instr:lib""); } @Test public void testInstrumentsManifestMergeDisabled() throws Exception { // This is the correct behavior where dependency manifests are not merged into the test apk. useConfiguration(""--experimental_disable_instrumentation_manifest_merge""); scratch.file( ""java/com/google/android/instr/BUILD"", ""android_binary("", "" name = 'b1',"", "" srcs = ['b1.java'],"", "" instruments = ':b2',"", "" deps = [':lib'],"", "" manifest = 'test/AndroidManifest.xml',"", "")"", ""android_library("", "" name = 'lib',"", "" manifest = 'lib/AndroidManifest.xml',"", "" exports_manifest = 1,"", "" resource_files = ['lib/res/values/strings.xml'],"", "")"", ""android_binary("", "" name = 'b2',"", "" srcs = ['b2.java'],"", "" deps = [':lib'],"", "" manifest = 'bin/AndroidManifest.xml',"", "")""); assertThat( getBinaryMergeeManifests(getConfiguredTarget(""//java/com/google/android/instr:b1"")) .values()) .isEmpty(); } @Test public void testOptimizedJavaResourcesDisabled() throws Exception { useConfiguration(""--noexperimental_get_android_java_resources_from_optimized_jar""); ConfiguredTarget ct = scratchConfiguredTarget( ""java/a"", ""a"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard.cfg'],"", "")""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ct)); Artifact extractedResources = getFirstArtifactEndingWith(artifacts, ""extracted_a_deploy.jar""); String args = Joiner.on("" "").join(getGeneratingSpawnActionArgs(extractedResources)); assertThat(args).contains(""/a_deploy.jar""); assertThat(args).doesNotContain(""a_proguard.jar""); } @Test public void testOptimizedJavaResourcesEnabled() throws Exception { useConfiguration(""--experimental_get_android_java_resources_from_optimized_jar""); ConfiguredTarget ct = scratchConfiguredTarget( ""java/a"", ""a"", ""android_binary("", "" name = 'a',"", "" srcs = ['A.java'],"", "" manifest = 'AndroidManifest.xml',"", "" proguard_specs = ['proguard.cfg'],"", "")""); Set artifacts = actionsTestUtil().artifactClosureOf(getFilesToBuild(ct)); Artifact extractedResources = getFirstArtifactEndingWith(artifacts, ""extracted_a_proguard.jar""); String args = Joiner.on("" "").join(getGeneratingSpawnActionArgs(extractedResources)); assertThat(args).doesNotContain(""a_deploy.jar""); assertThat(args).contains(""/a_proguard.jar""); } } ","topLevelResourceClassAction " "/* * Copyright 2021 ZXing authors * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.common; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import java.util.List; /** * Set of CharsetEncoders for a given input string * * Invariants: * - The list contains only encoders from CharacterSetECI (list is shorter then the list of encoders available on * the platform for which ECI values are defined). * - The list contains encoders at least one encoder for every character in the input. * - The first encoder in the list is always the ISO-8859-1 encoder even of no character in the input can be encoded * by it. * - If the input contains a character that is not in ISO-8859-1 then the last two entries in the list will be the * UTF-8 encoder and the UTF-16BE encoder. * * @author Alex Geller */ public final class ECIEncoderSet { // List of encoders that potentially encode characters not in ISO-8859-1 in one byte. private static final List ENCODERS = new ArrayList<>(); static { String[] names = { ""IBM437"", ""ISO-8859-2"", ""ISO-8859-3"", ""ISO-8859-4"", ""ISO-8859-5"", ""ISO-8859-6"", ""ISO-8859-7"", ""ISO-8859-8"", ""ISO-8859-9"", ""ISO-8859-10"", ""ISO-8859-11"", ""ISO-8859-13"", ""ISO-8859-14"", ""ISO-8859-15"", ""ISO-8859-16"", ""windows-1250"", ""windows-1251"", ""windows-1252"", ""windows-1256"", ""Shift_JIS"" }; for (String name : names) { if (CharacterSetECI.getCharacterSetECIByName(name) != null) { try { ENCODERS.add(Charset.forName(name).newEncoder()); } catch (UnsupportedCharsetException e) { // continue } } } } private final CharsetEncoder[] encoders; private final int priorityEncoderIndex; /** * Constructs an encoder set * * @param stringToEncode the string that needs to be encoded * @param priorityCharset The preferred {@link Charset} or null. * @param fnc1 fnc1 denotes the character in the input that represents the FNC1 character or -1 for a non-GS1 bar * code. When specified, it is considered an error to pass it as argument to the methods canEncode() or encode(). */ public ECIEncoderSet(String stringToEncode, Charset priorityCharset, int fnc1) { List [MASK] = new ArrayList<>(); //we always need the ISO-8859-1 encoder. It is the default encoding [MASK] .add(StandardCharsets.ISO_8859_1.newEncoder()); boolean needUnicodeEncoder = priorityCharset != null && priorityCharset.name().startsWith(""UTF""); //Walk over the input string and see if all characters can be encoded with the list of encoders for (int i = 0; i < stringToEncode.length(); i++) { boolean canEncode = false; for (CharsetEncoder encoder : [MASK] ) { char c = stringToEncode.charAt(i); if (c == fnc1 || encoder.canEncode(c)) { canEncode = true; break; } } if (!canEncode) { //for the character at position i we don't yet have an encoder in the list for (CharsetEncoder encoder : ENCODERS) { if (encoder.canEncode(stringToEncode.charAt(i))) { //Good, we found an encoder that can encode the character. We add him to the list and continue scanning //the input [MASK] .add(encoder); canEncode = true; break; } } } if (!canEncode) { //The character is not encodeable by any of the single byte encoders so we remember that we will need a //Unicode encoder. needUnicodeEncoder = true; } } if ( [MASK] .size() == 1 && !needUnicodeEncoder) { //the entire input can be encoded by the ISO-8859-1 encoder encoders = new CharsetEncoder[] { [MASK] .get(0) }; } else { // we need more than one single byte encoder or we need a Unicode encoder. // In this case we append a UTF-8 and UTF-16 encoder to the list encoders = new CharsetEncoder[ [MASK] .size() + 2]; int index = 0; for (CharsetEncoder encoder : [MASK] ) { encoders[index++] = encoder; } encoders[index] = StandardCharsets.UTF_8.newEncoder(); encoders[index + 1] = StandardCharsets.UTF_16BE.newEncoder(); } //Compute priorityEncoderIndex by looking up priorityCharset in encoders int priorityEncoderIndexValue = -1; if (priorityCharset != null) { for (int i = 0; i < encoders.length; i++) { if (encoders[i] != null && priorityCharset.name().equals(encoders[i].charset().name())) { priorityEncoderIndexValue = i; break; } } } priorityEncoderIndex = priorityEncoderIndexValue; //invariants assert encoders[0].charset().equals(StandardCharsets.ISO_8859_1); } public int length() { return encoders.length; } public String getCharsetName(int index) { assert index < length(); return encoders[index].charset().name(); } public Charset getCharset(int index) { assert index < length(); return encoders[index].charset(); } public int getECIValue(int encoderIndex) { return CharacterSetECI.getCharacterSetECI(encoders[encoderIndex].charset()).getValue(); } /* * returns -1 if no priority charset was defined */ public int getPriorityEncoderIndex() { return priorityEncoderIndex; } public boolean canEncode(char c, int encoderIndex) { assert encoderIndex < length(); CharsetEncoder encoder = encoders[encoderIndex]; return encoder.canEncode("""" + c); } public byte[] encode(char c, int encoderIndex) { assert encoderIndex < length(); CharsetEncoder encoder = encoders[encoderIndex]; assert encoder.canEncode("""" + c); return ("""" + c).getBytes(encoder.charset()); } public byte[] encode(String s, int encoderIndex) { assert encoderIndex < length(); CharsetEncoder encoder = encoders[encoderIndex]; return s.getBytes(encoder.charset()); } } ","neededEncoders " "/* * Copyright 2014 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.realm; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static io.realm.TestHelper.testNoObjectFound; import static io.realm.TestHelper.testOneObjectFound; import static io.realm.internal.test.ExtraTests.assertArrayEquals; import android.content.Context; import android.os.Build; import android.os.Looper; import android.os.StrictMode; import android.os.SystemClock; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.UiThreadTestRule; import junit.framework.AssertionFailedError; import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.hamcrest.CoreMatchers; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Random; import java.util.Scanner; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.Cat; import io.realm.entities.CyclicType; import io.realm.entities.CyclicTypePrimaryKey; import io.realm.entities.DefaultValueConstructor; import io.realm.entities.DefaultValueFromOtherConstructor; import io.realm.entities.DefaultValueOfField; import io.realm.entities.DefaultValueOverwriteNullLink; import io.realm.entities.DefaultValueSetter; import io.realm.entities.Dog; import io.realm.entities.DogPrimaryKey; import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Object4957; import io.realm.entities.Owner; import io.realm.entities.OwnerPrimaryKey; import io.realm.entities.PrimaryKeyAsBoxedByte; import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsLong; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.PrimaryKeyMix; import io.realm.entities.PrimaryKeyRequiredAsBoxedByte; import io.realm.entities.PrimaryKeyRequiredAsBoxedInteger; import io.realm.entities.PrimaryKeyRequiredAsBoxedLong; import io.realm.entities.PrimaryKeyRequiredAsBoxedShort; import io.realm.entities.PrimaryKeyRequiredAsString; import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringAndInt; import io.realm.entities.StringOnly; import io.realm.entities.StringOnlyReadOnly; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.OsSharedRealm; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.BlockingLooperThread; import io.realm.util.RealmThread; @RunWith(AndroidJUnit4.class) public class RealmTests { private final static int TEST_DATA_SIZE = 10; @Rule public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final TemporaryFolder tmpFolder = new TemporaryFolder(); @Rule public final ExpectedException thrown = ExpectedException.none(); public final BlockingLooperThread looperThread = new BlockingLooperThread(); private Context context; private Realm realm; private List columnData = new ArrayList() {{ add(AllTypes.FIELD_DOUBLE); add(AllTypes.FIELD_FLOAT); add(AllTypes.FIELD_LONG); add(AllTypes.FIELD_DECIMAL128); add(AllTypes.FIELD_BOOLEAN); add(AllTypes.FIELD_DATE); add(AllTypes.FIELD_OBJECT_ID); add(AllTypes.FIELD_STRING); add(AllTypes.FIELD_BINARY); add(AllTypes.FIELD_UUID); add(AllTypes.FIELD_REALM_ANY); }}; private RealmConfiguration realmConfig; @Before public void setUp() { // Injecting the Instrumentation instance is required // for your test to run with AndroidJUnitRunner. context = InstrumentationRegistry.getInstrumentation().getContext(); realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); } @After public void tearDown() { if (realm != null) { realm.close(); } } private void populateTestRealm(Realm realm, int objects) { realm.beginTransaction(); realm.deleteAll(); for (int i = 0; i < objects; ++i) { AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDate(new Date()); allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.234567F + i); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(i + ""12345""))); allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(i))); allTypes.setColumnRealmAny(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(i)))); allTypes.setColumnString(""test data "" + i); allTypes.setColumnLong(i); NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); nonLatinFieldNames.set베타(1.234567F + i); nonLatinFieldNames.setΒήτα(1.234567F + i); } realm.commitTransaction(); } private void populateTestRealm() { populateTestRealm(realm, TEST_DATA_SIZE); } @Test public void getInstance_writeProtectedFile() throws IOException { String REALM_FILE = ""readonly.realm""; File folder = configFactory.getRoot(); File realmFile = new File(folder, REALM_FILE); assertFalse(realmFile.exists()); assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); try { Realm.getInstance(configFactory.createConfigurationBuilder() .directory(folder) .name(REALM_FILE) .build()); fail(); } catch (RealmFileException expected) { assertEquals(RealmFileException.Kind.PERMISSION_DENIED, expected.getKind()); } } @Test public void getInstance_writeProtectedFileWithContext() throws IOException { String REALM_FILE = ""readonly.realm""; File folder = configFactory.getRoot(); File realmFile = new File(folder, REALM_FILE); assertFalse(realmFile.exists()); assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); try { Realm.getInstance(configFactory.createConfigurationBuilder().directory(folder).name(REALM_FILE).build()); fail(); } catch (RealmFileException expected) { assertEquals(RealmFileException.Kind.PERMISSION_DENIED, expected.getKind()); } } @Test public void getInstance_twiceWhenRxJavaUnavailable() { // Test for https://github.com/realm/realm-java/issues/2416 // Though it's not a recommended way to create multiple configuration instance with the same parameter, it's legal. final RealmConfiguration configuration1 = configFactory.createConfiguration(""no_RxJava.realm""); TestHelper.emulateRxJavaUnavailable(configuration1); final RealmConfiguration configuration2 = configFactory.createConfiguration(""no_RxJava.realm""); TestHelper.emulateRxJavaUnavailable(configuration2); final Realm realm1 = Realm.getInstance(configuration1); //noinspection TryFinallyCanBeTryWithResources try { final Realm realm2 = Realm.getInstance(configuration2); realm2.close(); } finally { realm1.close(); } } @Test public void checkIfValid() { // checkIfValid() must not throw any Exception against valid Realm instance. realm.checkIfValid(); realm.close(); try { realm.checkIfValid(); fail(""closed Realm instance must throw IllegalStateException.""); } catch (IllegalStateException ignored) { } realm = null; } @Test public void getInstance() { assertNotNull(""Realm.getInstance unexpectedly returns null"", realm); assertTrue(""Realm.getInstance does not contain expected table"", realm.getSchema().contains(AllTypes.CLASS_NAME)); } @Test public void where() { populateTestRealm(); RealmResults [MASK] = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE, [MASK] .size()); } @Test public void where_throwsIfClassArgIsNotASubtype() { try { realm.where(RealmObject.class); fail(); } catch (IllegalArgumentException ignore) { } try { realm.where(RealmModel.class); fail(); } catch (IllegalArgumentException ignore) { } } // Note that this test is relying on the values set while initializing the test dataset // TODO Move to RealmQueryTests? @Test public void where_queryResults() throws IOException { populateTestRealm(realm, 159); RealmResults [MASK] = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 33).findAll(); assertEquals(1, [MASK] .size()); [MASK] = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 3333).findAll(); assertEquals(0, [MASK] .size()); [MASK] = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""test data 0"").findAll(); assertEquals(1, [MASK] .size()); [MASK] = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""test data 0"", Case.INSENSITIVE).findAll(); assertEquals(1, [MASK] .size()); [MASK] = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""Test data 0"", Case.SENSITIVE).findAll(); assertEquals(0, [MASK] .size()); } // TODO Move to RealmQueryTests? @Test public void where_equalTo_wrongFieldTypeAsInput() throws IOException { populateTestRealm(); for (int i = 0; i < columnData.size(); i++) { // Realm queries applies coercion on numerical values boolean NON_NUMERICAL_COLUMN = (i > 4) && (i != 10); // Realm queries applies coercion on objectid and date boolean NON_OBJECT_OR_DATE = ((i <= 4) || (i > 6)) && (i != 10); // Realm queries applies coercion on string and binary boolean NON_STRING_OR_BINARY = ((i <= 6) || (i > 8)) && (i != 10); try { realm.where(AllTypes.class).equalTo(columnData.get(i), 13.37D).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), 13.3711F).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), 1337).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new Decimal128(new BigDecimal(i + ""12345""))).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), true).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new Date()).findAll(); if (NON_OBJECT_OR_DATE) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new ObjectId(TestHelper.generateObjectIdHexString(i))).findAll(); if (NON_OBJECT_OR_DATE) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), ""test"").findAll(); if (NON_STRING_OR_BINARY) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new byte[] {1, 2, 3}).findAll(); if (NON_STRING_OR_BINARY) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), UUID.fromString(TestHelper.generateUUIDString(i))).findAll(); if ((i != 9) && (i != 10)) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } } } // TODO Move to RealmQueryTests? @Test public void where_equalTo_invalidFieldName() throws IOException { try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", 33).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", ""test"").findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", true).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", Math.PI).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", Math.PI).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } } @Test public void beginTransaction() throws IOException { populateTestRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnFloat(3.14F); allTypes.setColumnString(""a unique string""); realm.commitTransaction(); RealmResults [MASK] = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE + 1, [MASK] .size()); [MASK] = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""a unique string"").findAll(); assertEquals(1, [MASK] .size()); [MASK] = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 3.14F).findAll(); assertEquals(1, [MASK] .size()); } @Test public void nestedTransaction() { realm.beginTransaction(); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertTrue(e.getMessage().startsWith(""The Realm is already in a write transaction"")); } realm.commitTransaction(); } private enum Method { METHOD_BEGIN, METHOD_COMMIT, METHOD_CANCEL, METHOD_EXECUTE_TRANSACTION, METHOD_EXECUTE_TRANSACTION_ASYNC, METHOD_DELETE_TYPE, METHOD_DELETE_ALL, METHOD_CREATE_OBJECT, METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY, METHOD_COPY_TO_REALM, METHOD_COPY_TO_REALM_OR_UPDATE, METHOD_CREATE_ALL_FROM_JSON, METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON, METHOD_CREATE_FROM_JSON, METHOD_CREATE_OR_UPDATE_FROM_JSON, METHOD_INSERT_COLLECTION, METHOD_INSERT_OBJECT, METHOD_INSERT_OR_UPDATE_COLLECTION, METHOD_INSERT_OR_UPDATE_OBJECT } // Calling methods on a wrong thread will fail. private boolean runMethodOnWrongThread(final Method method) throws InterruptedException, ExecutionException { if (method != Method.METHOD_BEGIN) { realm.beginTransaction(); realm.createObject(Dog.class); } ExecutorService executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new Callable() { @Override public Boolean call() throws Exception { try { switch (method) { case METHOD_BEGIN: realm.beginTransaction(); break; case METHOD_COMMIT: realm.commitTransaction(); break; case METHOD_CANCEL: realm.cancelTransaction(); break; case METHOD_EXECUTE_TRANSACTION: realm.executeTransaction(realm -> fail()); break; case METHOD_EXECUTE_TRANSACTION_ASYNC: realm.executeTransactionAsync(realm -> fail()); break; case METHOD_DELETE_TYPE: realm.delete(AllTypes.class); break; case METHOD_DELETE_ALL: realm.deleteAll(); break; case METHOD_CREATE_OBJECT: realm.createObject(AllTypes.class); break; case METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY: realm.createObject(AllJavaTypes.class, 1L); break; case METHOD_COPY_TO_REALM: realm.copyToRealm(new AllTypes()); break; case METHOD_COPY_TO_REALM_OR_UPDATE: realm.copyToRealm(new AllTypesPrimaryKey()); break; case METHOD_CREATE_ALL_FROM_JSON: realm.createAllFromJson(AllTypes.class, ""[{}]""); break; case METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON: realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, ""[{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}]""); break; case METHOD_CREATE_FROM_JSON: realm.createObjectFromJson(AllTypes.class, ""{}""); break; case METHOD_CREATE_OR_UPDATE_FROM_JSON: realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, ""{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}""); break; case METHOD_INSERT_COLLECTION: realm.insert(Arrays.asList(new AllTypes(), new AllTypes())); break; case METHOD_INSERT_OBJECT: realm.insert(new AllTypes()); break; case METHOD_INSERT_OR_UPDATE_COLLECTION: realm.insert(Arrays.asList(new AllTypesPrimaryKey(), new AllTypesPrimaryKey())); break; case METHOD_INSERT_OR_UPDATE_OBJECT: realm.insertOrUpdate(new AllTypesPrimaryKey()); break; } return false; } catch (IllegalStateException ignored) { return true; } catch (RealmException jsonFailure) { // TODO: Eew. Reconsider how our JSON methods reports failure. See https://github.com/realm/realm-java/issues/1594 return (jsonFailure.getMessage().equals(""Could not map Json"")); } } }); boolean result = future.get(); if (method != Method.METHOD_BEGIN) { realm.cancelTransaction(); } return result; } @Test public void methodCalledOnWrongThread() throws ExecutionException, InterruptedException { for (Method method : Method.values()) { assertTrue(method.toString(), runMethodOnWrongThread(method)); } } // Calling methods on a wrong thread will fail. private boolean runMethodOnClosedRealm(final Method method) throws InterruptedException, ExecutionException { try { switch (method) { case METHOD_BEGIN: realm.beginTransaction(); break; case METHOD_COMMIT: realm.commitTransaction(); break; case METHOD_CANCEL: realm.cancelTransaction(); break; case METHOD_EXECUTE_TRANSACTION: realm.executeTransaction(realm -> fail()); break; case METHOD_EXECUTE_TRANSACTION_ASYNC: realm.executeTransactionAsync(realm -> fail()); break; case METHOD_DELETE_TYPE: realm.delete(AllTypes.class); break; case METHOD_DELETE_ALL: realm.deleteAll(); break; case METHOD_CREATE_OBJECT: realm.createObject(AllTypes.class); break; case METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY: realm.createObject(AllJavaTypes.class, 1L); break; case METHOD_COPY_TO_REALM: realm.copyToRealm(new AllTypes()); break; case METHOD_COPY_TO_REALM_OR_UPDATE: realm.copyToRealm(new AllTypesPrimaryKey()); break; case METHOD_CREATE_ALL_FROM_JSON: realm.createAllFromJson(AllTypes.class, ""[{}]""); break; case METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON: realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, ""[{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}]""); break; case METHOD_CREATE_FROM_JSON: realm.createObjectFromJson(AllTypes.class, ""{}""); break; case METHOD_CREATE_OR_UPDATE_FROM_JSON: realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, ""{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}""); break; case METHOD_INSERT_COLLECTION: realm.insert(Arrays.asList(new AllTypes(), new AllTypes())); break; case METHOD_INSERT_OBJECT: realm.insert(new AllTypes()); break; case METHOD_INSERT_OR_UPDATE_COLLECTION: realm.insert(Arrays.asList(new AllTypesPrimaryKey(), new AllTypesPrimaryKey())); break; case METHOD_INSERT_OR_UPDATE_OBJECT: realm.insertOrUpdate(new AllTypesPrimaryKey()); break; } return false; } catch (IllegalStateException ignored) { return true; } catch (RealmException jsonFailure) { // TODO: Eew. Reconsider how our JSON methods reports failure. See https://github.com/realm/realm-java/issues/1594 return (jsonFailure.getMessage().equals(""Could not map Json"")); } } @Test public void methodCalledOnClosedRealm() throws ExecutionException, InterruptedException { realm.close(); for (Method method : Method.values()) { assertTrue(method.toString(), runMethodOnClosedRealm(method)); } } @Test public void commitTransaction() { populateTestRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnBoolean(true); realm.commitTransaction(); RealmResults [MASK] = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE + 1, [MASK] .size()); } @Test(expected = IllegalStateException.class) public void commitTransaction_afterCancelTransaction() { realm.beginTransaction(); realm.cancelTransaction(); realm.commitTransaction(); } @Test(expected = IllegalStateException.class) public void commitTransaction_twice() { realm.beginTransaction(); realm.commitTransaction(); realm.commitTransaction(); } @Test public void cancelTransaction() { populateTestRealm(); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.cancelTransaction(); assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).count()); try { realm.cancelTransaction(); fail(); } catch (IllegalStateException ignored) { } } @Test public void executeTransaction_null() { OsSharedRealm.VersionID oldVersion = realm.sharedRealm.getVersionID(); try { realm.executeTransaction(null); fail(""null transaction should throw""); } catch (IllegalArgumentException ignored) { } OsSharedRealm.VersionID newVersion = realm.sharedRealm.getVersionID(); assertEquals(oldVersion, newVersion); } @Test public void executeTransaction_success() { assertEquals(0, realm.where(Owner.class).count()); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName(""Owner""); } }); assertEquals(1, realm.where(Owner.class).count()); } @Test public void executeTransaction_canceled() { final AtomicReference thrownException = new AtomicReference(null); assertEquals(0, realm.where(Owner.class).count()); try { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName(""Owner""); thrownException.set(new RuntimeException(""Boom"")); throw thrownException.get(); } }); } catch (RuntimeException e) { //noinspection ThrowableResultOfMethodCallIgnored assertTrue(e == thrownException.get()); } assertEquals(0, realm.where(Owner.class).count()); } @Test public void executeTransaction_cancelInsideClosureThrowsException() { assertEquals(0, realm.where(Owner.class).count()); TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); try { RealmLog.add(testLogger); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName(""Owner""); realm.cancelTransaction(); throw new RuntimeException(""Boom""); } }); } catch (RuntimeException ignored) { // Ensures that we pass a valuable error message to the logger for developers. assertEquals(""Could not cancel transaction, not currently in a transaction."", testLogger.message); } finally { RealmLog.remove(testLogger); } assertEquals(0, realm.where(Owner.class).count()); } @Test @UiThreadTest public void executeTransaction_mainThreadWritesAllowed() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowWritesOnUiThread(true) .name(""ui_realm"") .build(); Realm uiRealm = Realm.getInstance(configuration); uiRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.insert(new Dog(""Snuffles"")); } }); RealmResults results = uiRealm.where(Dog.class).equalTo(""name"", ""Snuffles"").findAll(); assertEquals(1, results.size()); assertNotNull(results.first()); assertEquals(""Snuffles"", Objects.requireNonNull(results.first()).getName()); uiRealm.close(); } @Test @UiThreadTest public void executeTransaction_mainThreadWritesNotAllowed() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowWritesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // no-op } }); fail(""the call to executeTransaction should have failed, this line should not be reached.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowWritesOnUiThread"")); } } @Test public void executeTransaction_runsOnNonUiThread() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowWritesOnUiThread(false) .name(""ui_realm"") .build(); Realm uiRealm = Realm.getInstance(configuration); uiRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // no-op } }); uiRealm.close(); } @Test public void delete_type() { // ** Deletes non existing table should succeed. realm.beginTransaction(); realm.delete(AllTypes.class); realm.commitTransaction(); // ** Deletes existing class, but leaves other classes classes. // Adds two classes. populateTestRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName(""Castro""); realm.commitTransaction(); // Clears. realm.beginTransaction(); realm.delete(Dog.class); realm.commitTransaction(); // Checks one class is cleared but other class is still there. RealmResults [MASK] Types = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE, [MASK] Types.size()); RealmResults [MASK] Dogs = realm.where(Dog.class).findAll(); assertEquals(0, [MASK] Dogs.size()); // ** delete() must throw outside a transaction. try { realm.delete(AllTypes.class); fail(""Expected exception""); } catch (IllegalStateException ignored) { } } private void createAndTestFilename(String language, String fileName) { RealmConfiguration realmConfig = configFactory.createConfiguration(fileName); Realm realm1 = Realm.getInstance(realmConfig); realm1.beginTransaction(); Dog dog1 = realm1.createObject(Dog.class); dog1.setName(""Rex""); realm1.commitTransaction(); realm1.close(); File file = new File(realmConfig.getPath()); assertTrue(language, file.exists()); Realm realm2 = Realm.getInstance(realmConfig); Dog dog2 = realm2.where(Dog.class).findFirst(); assertEquals(language, ""Rex"", dog2.getName()); realm2.close(); } // TODO Move to RealmConfigurationTests? @Test public void realmConfiguration_fileName() { createAndTestFilename(""American"", ""Washington""); createAndTestFilename(""Danish"", ""København""); createAndTestFilename(""Russian"", ""Москва""); createAndTestFilename(""Greek"", ""Αθήνα""); createAndTestFilename(""Chinese"", ""北京市""); createAndTestFilename(""Korean"", ""서울시""); createAndTestFilename(""Arabic"", ""الرياض""); createAndTestFilename(""India"", ""नई दिल्ली""); createAndTestFilename(""Japanese"", ""東京都""); } @Test public void utf8Tests() { realm.beginTransaction(); realm.delete(AllTypes.class); realm.commitTransaction(); String file = ""assets/unicode_codepoints.csv""; Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(file), ""UTF-8""); int i = 0; String currentUnicode = null; try { realm.beginTransaction(); while (scanner.hasNextLine()) { currentUnicode = scanner.nextLine(); char[] chars = Character.toChars(Integer.parseInt(currentUnicode, 16)); String codePoint = new String(chars); AllTypes o = realm.createObject(AllTypes.class); o.setColumnLong(i); o.setColumnString(codePoint); if (i > 1) { assertEquals(""Codepoint: "" + i + "" / "" + currentUnicode, codePoint, o.getColumnString()); // codepoint 0 is NULL, ignore for now. } i++; } realm.commitTransaction(); } catch (Exception e) { fail(""Failure, Codepoint: "" + i + "" / "" + currentUnicode + "" "" + e.getMessage()); } } private List getCharacterArray() { List chars_array = new ArrayList(); String file = ""assets/unicode_codepoints.csv""; Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(file), ""UTF-8""); int i = 0; String currentUnicode = null; try { while (scanner.hasNextLine()) { currentUnicode = scanner.nextLine(); char[] chars = Character.toChars(Integer.parseInt(currentUnicode, 16)); String codePoint = new String(chars); chars_array.add(codePoint); i++; } } catch (Exception e) { fail(""Failure, Codepoint: "" + i + "" / "" + currentUnicode + "" "" + e.getMessage()); } return chars_array; } // The test writes and reads random Strings. @Test public void unicodeStrings() { List charsArray = getCharacterArray(); // Change seed value for new random values. long seed = 20; Random random = new Random(seed); StringBuilder testChar = new StringBuilder(); realm.beginTransaction(); for (int i = 0; i < 1000; i++) { testChar.setLength(0); int length = random.nextInt(25); for (int j = 0; j < length; j++) { testChar.append(charsArray.get(random.nextInt(27261))); } StringOnly stringOnly = realm.createObject(StringOnly.class); // tests setter stringOnly.setChars(testChar.toString()); // tests getter realm.where(StringOnly.class).findFirst().getChars(); realm.delete(StringOnly.class); } realm.cancelTransaction(); } @Test public void getInstance_referenceCounting() { // At this point reference count should be one because of the setUp method. try { realm.where(AllTypes.class).count(); } catch (IllegalStateException e) { fail(); } // Makes sure the reference counter is per realm file. RealmConfiguration anotherConfig = configFactory.createConfiguration(""anotherRealm.realm""); Realm.deleteRealm(anotherConfig); Realm otherRealm = Realm.getInstance(anotherConfig); // Raises the reference. Realm realm = null; try { realm = Realm.getInstance(configFactory.createConfiguration()); } finally { if (realm != null) { realm.close(); } } try { // This should not fail because the reference is now 1. if (realm != null) { realm.where(AllTypes.class).count(); } } catch (IllegalStateException e) { fail(); } this.realm.close(); try { this.realm.where(AllTypes.class).count(); fail(); } catch (IllegalStateException ignored) { } try { otherRealm.where(AllTypes.class).count(); } catch (IllegalStateException e) { fail(); } finally { otherRealm.close(); } try { otherRealm.where(AllTypes.class).count(); fail(); } catch (IllegalStateException ignored) { } } @Test public void getInstance_referenceCounting_doubleClose() { realm.close(); realm.close(); // Counts down once too many. Counter is now potentially negative. realm = Realm.getInstance(configFactory.createConfiguration()); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); RealmResults queryResult = realm.where(AllTypes.class).findAll(); assertEquals(allTypes, queryResult.get(0)); realm.commitTransaction(); realm.close(); // This might not close the Realm if the reference count is wrong. // This should now fail due to the Realm being fully closed. thrown.expect(IllegalStateException.class); allTypes.getColumnString(); } @Test public void writeCopyTo() throws IOException { RealmConfiguration configA = configFactory.createConfiguration(""file1.realm""); RealmConfiguration configB = configFactory.createConfiguration(""file2.realm""); Realm.deleteRealm(configA); Realm.deleteRealm(configB); Realm realm1 = null; try { realm1 = Realm.getInstance(configA); realm1.beginTransaction(); AllTypes allTypes = realm1.createObject(AllTypes.class); allTypes.setColumnString(""Hello World""); realm1.commitTransaction(); realm1.writeCopyTo(new File(configB.getPath())); } finally { if (realm1 != null) { realm1.close(); } } // Copy is compacted i.e. smaller than original. File file1 = new File(configA.getPath()); File file2 = new File(configB.getPath()); assertTrue(file1.length() >= file2.length()); Realm realm2 = null; try { // Contents is copied too. realm2 = Realm.getInstance(configB); RealmResults results = realm2.where(AllTypes.class).findAll(); assertEquals(1, results.size()); assertEquals(""Hello World"", results.first().getColumnString()); } finally { if (realm2 != null) { realm2.close(); } } } @Test public void compactRealm() { final RealmConfiguration configuration = realm.getConfiguration(); realm.close(); realm = null; assertTrue(Realm.compactRealm(configuration)); realm = Realm.getInstance(configuration); } @Test public void compactRealm_failsIfOpen() { assertFalse(Realm.compactRealm(realm.getConfiguration())); } @Test public void compactRealm_encryptedEmptyRealm() { RealmConfiguration realmConfig = configFactory.createConfiguration(""enc.realm"", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); realm.close(); assertTrue(Realm.compactRealm(realmConfig)); realm = Realm.getInstance(realmConfig); assertFalse(realm.isClosed()); assertTrue(realm.isEmpty()); realm.close(); } @Test public void compactRealm_encryptedPopulatedRealm() { final int DATA_SIZE = 100; RealmConfiguration realmConfig = configFactory.createConfiguration(""enc.realm"", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); populateTestRealm(realm, DATA_SIZE); realm.close(); assertTrue(Realm.compactRealm(realmConfig)); realm = Realm.getInstance(realmConfig); assertFalse(realm.isClosed()); assertEquals(DATA_SIZE, realm.where(AllTypes.class).count()); realm.close(); } @Test public void compactRealm_emptyRealm() throws IOException { final String REALM_NAME = ""test.realm""; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); realm.close(); long before = new File(realmConfig.getPath()).length(); assertTrue(Realm.compactRealm(realmConfig)); long after = new File(realmConfig.getPath()).length(); assertTrue(before >= after); } @Test public void compactRealm_populatedRealm() throws IOException { final String REALM_NAME = ""test.realm""; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); populateTestRealm(realm, 100); realm.close(); long before = new File(realmConfig.getPath()).length(); assertTrue(Realm.compactRealm(realmConfig)); long after = new File(realmConfig.getPath()).length(); assertTrue(before >= after); } @Test public void compactRealm_onExternalStorage() { final File externalFilesDir = context.getExternalFilesDir(null); final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(externalFilesDir) .name(""external.realm"") .build(); Realm.deleteRealm(config); Realm realm = Realm.getInstance(config); realm.close(); assertTrue(Realm.compactRealm(config)); realm = Realm.getInstance(config); realm.close(); Realm.deleteRealm(config); } private void populateTestRealmForCompact(Realm realm, int sizeInMB) { byte[] oneMBData = new byte[1024 * 1024]; realm.beginTransaction(); for (int i = 0; i < sizeInMB; i++) { realm.createObject(AllTypes.class).setColumnBinary(oneMBData); } realm.commitTransaction(); } private Pair populateTestRealmAndCompactOnLaunch(CompactOnLaunchCallback compactOnLaunch) { return populateTestRealmAndCompactOnLaunch(compactOnLaunch, 1); } private Pair populateTestRealmAndCompactOnLaunch(CompactOnLaunchCallback compactOnLaunch, int sizeInMB) { final String REALM_NAME = ""test.realm""; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); populateTestRealmForCompact(realm, sizeInMB); realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); realm.close(); long before = new File(realmConfig.getPath()).length(); if (compactOnLaunch != null) { realmConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .compactOnLaunch(compactOnLaunch) .build(); } else { realmConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .compactOnLaunch() .build(); } realm = Realm.getInstance(realmConfig); realm.close(); long after = new File(realmConfig.getPath()).length(); return new Pair(before, after); } @Test public void compactOnLaunch_shouldCompact() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { assertTrue(totalBytes > usedBytes); return true; } }); assertTrue(results.first > results.second); } @Test public void compactOnLaunch_shouldNotCompact() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { assertTrue(totalBytes > usedBytes); return false; } }); assertEquals(results.first, results.second); } @Test public void compactOnLaunch_multipleThread() throws IOException { final String REALM_NAME = ""test.realm""; final AtomicInteger compactOnLaunchCount = new AtomicInteger(0); final RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .compactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { compactOnLaunchCount.incrementAndGet(); return true; } }) .build(); Realm realm = Realm.getInstance(realmConfig); realm.close(); // WARNING: We need to init the schema first and close the Realm to make sure the relevant logic works in Object // Store. See https://github.com/realm/realm-object-store/blob/master/src/shared_realm.cpp#L58 // Called once. assertEquals(1, compactOnLaunchCount.get()); realm = Realm.getInstance(realmConfig); assertEquals(2, compactOnLaunchCount.get()); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm bgRealm = Realm.getInstance(realmConfig); bgRealm.close(); // compactOnLaunch should not be called anymore! assertEquals(2, compactOnLaunchCount.get()); } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { fail(); } realm.close(); assertEquals(2, compactOnLaunchCount.get()); } @Test public void compactOnLaunch_insufficientAmount() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { final long thresholdSize = 50 * 1024 * 1024; return (totalBytes > thresholdSize) && (((double) usedBytes / (double) totalBytes) < 0.5); } }, 1); final long thresholdSize = 50 * 1024 * 1024; assertTrue(results.first < thresholdSize); assertEquals(results.first, results.second); } @Test public void compactOnLaunch_throwsInTheCallback() { final RuntimeException exception = new RuntimeException(); final RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .name(""compactThrowsTest"") .compactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { throw exception; } }) .build(); Realm realm = null; try { realm = Realm.getInstance(realmConfig); fail(); } catch (RuntimeException expected) { assertSame(exception, expected); } finally { if (realm != null) { realm.close(); } } } @Test public void defaultCompactOnLaunch() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(null, 50); final long thresholdSize = 50 * 1024 * 1024; assertTrue(results.first > thresholdSize); assertTrue(results.first > results.second); } @Test public void defaultCompactOnLaunch_onlyCallback() { DefaultCompactOnLaunchCallback callback = new DefaultCompactOnLaunchCallback(); final long thresholdSize = 50 * 1024 * 1024; final long big = thresholdSize + 1024; assertFalse(callback.shouldCompact(big, (long) (big * 0.6))); assertTrue(callback.shouldCompact(big, (long) (big * 0.3))); final long small = thresholdSize - 1024; assertFalse(callback.shouldCompact(small, (long) (small * 0.6))); assertFalse(callback.shouldCompact(small, (long) (small * 0.3))); } @Test public void defaultCompactOnLaunch_insufficientAmount() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(null, 1); final long thresholdSize = 50 * 1024 * 1024; assertTrue(results.first < thresholdSize); assertEquals(results.first, results.second); } @Test public void copyToRealm_null() { realm.beginTransaction(); try { realm.copyToRealm((AllTypes) null); fail(""Copying null objects into Realm should not be allowed""); } catch (IllegalArgumentException ignored) { } finally { realm.cancelTransaction(); } } @Test public void copyToRealm_managedObject() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnString(""Test""); realm.commitTransaction(); realm.beginTransaction(); AllTypes copiedAllTypes = realm.copyToRealm(allTypes); realm.commitTransaction(); assertTrue(allTypes == copiedAllTypes); } @Test public void copyToRealm_fromOtherRealm() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnString(""Test""); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(""12345""))); allTypes.setColumnObjectId(new ObjectId(TestHelper.randomObjectIdHexString())); allTypes.setColumnUUID(UUID.randomUUID()); allTypes.setColumnRealmAny(RealmAny.valueOf(UUID.randomUUID())); realm.commitTransaction(); RealmConfiguration realmConfig = configFactory.createConfiguration(""other-realm""); Realm otherRealm = Realm.getInstance(realmConfig); otherRealm.beginTransaction(); AllTypes copiedAllTypes = otherRealm.copyToRealm(allTypes); otherRealm.commitTransaction(); assertNotSame(allTypes, copiedAllTypes); // Same object in different Realms is not the same. assertEquals(allTypes.getColumnString(), copiedAllTypes.getColumnString()); // But data is still the same. otherRealm.close(); } @Test public void copyToRealm() { Date date = new Date(); Dog dog = new Dog(); dog.setName(""Fido""); RealmList list = new RealmList(); list.add(dog); AllTypes allTypes = new AllTypes(); allTypes.setColumnString(""String""); allTypes.setColumnLong(1L); allTypes.setColumnFloat(1F); allTypes.setColumnDouble(1D); allTypes.setColumnBoolean(true); allTypes.setColumnDate(date); allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(""12345""))); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(7))); allTypes.setColumnRealmAny(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(6)))); allTypes.setColumnRealmObject(dog); allTypes.setColumnRealmList(list); allTypes.setColumnStringList(new RealmList(""1"")); allTypes.setColumnBinaryList(new RealmList(new byte[] {1})); allTypes.setColumnBooleanList(new RealmList(true)); allTypes.setColumnLongList(new RealmList(1L)); allTypes.setColumnDoubleList(new RealmList(1D)); allTypes.setColumnFloatList(new RealmList(1F)); allTypes.setColumnDateList(new RealmList(new Date(1L))); allTypes.setColumnDecimal128List(new RealmList(new Decimal128(new BigDecimal(""54321"")))); allTypes.setColumnObjectIdList(new RealmList(new ObjectId(TestHelper.generateObjectIdHexString(5)))); allTypes.setColumnUUIDList(new RealmList<>(UUID.fromString(TestHelper.generateUUIDString(5)))); allTypes.setColumnRealmAnyList(new RealmList<>(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(7))))); realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(allTypes); realm.commitTransaction(); assertNotSame(allTypes, realmTypes); // Objects should not be considered equal. assertEquals(allTypes.getColumnString(), realmTypes.getColumnString()); // But they contain the same data. assertEquals(allTypes.getColumnLong(), realmTypes.getColumnLong()); assertEquals(allTypes.getColumnFloat(), realmTypes.getColumnFloat(), 0); assertEquals(allTypes.getColumnDouble(), realmTypes.getColumnDouble(), 0); assertEquals(allTypes.isColumnBoolean(), realmTypes.isColumnBoolean()); assertEquals(allTypes.getColumnDate(), realmTypes.getColumnDate()); assertArrayEquals(allTypes.getColumnBinary(), realmTypes.getColumnBinary()); assertEquals(allTypes.getColumnDecimal128(), realmTypes.getColumnDecimal128()); assertEquals(allTypes.getColumnObjectId(), realmTypes.getColumnObjectId()); assertEquals(allTypes.getColumnUUID(), realmTypes.getColumnUUID()); assertEquals(allTypes.getColumnRealmAny(), realmTypes.getColumnRealmAny()); assertEquals(allTypes.getColumnRealmObject().getName(), dog.getName()); assertEquals(list.size(), realmTypes.getColumnRealmList().size()); //noinspection ConstantConditions assertEquals(list.get(0).getName(), realmTypes.getColumnRealmList().get(0).getName()); assertEquals(1, realmTypes.getColumnStringList().size()); assertEquals(""1"", realmTypes.getColumnStringList().get(0)); assertEquals(1, realmTypes.getColumnBooleanList().size()); assertEquals(true, realmTypes.getColumnBooleanList().get(0)); assertEquals(1, realmTypes.getColumnBinaryList().size()); assertArrayEquals(new byte[] {1}, realmTypes.getColumnBinaryList().get(0)); assertEquals(1, realmTypes.getColumnLongList().size()); assertEquals((Long) 1L, realmTypes.getColumnLongList().get(0)); assertEquals(1, realmTypes.getColumnDoubleList().size()); assertEquals((Double) 1D, realmTypes.getColumnDoubleList().get(0)); assertEquals(1, realmTypes.getColumnFloatList().size()); assertEquals((Float) 1F, realmTypes.getColumnFloatList().get(0)); assertEquals(1, realmTypes.getColumnDateList().size()); assertEquals(new Date(1), realmTypes.getColumnDateList().get(0)); assertEquals(1, realmTypes.getColumnDecimal128List().size()); assertEquals(new Decimal128(new BigDecimal(""54321"")), realmTypes.getColumnDecimal128List().get(0)); assertEquals(1, realmTypes.getColumnObjectIdList().size()); assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(5)), realmTypes.getColumnObjectIdList().get(0)); assertEquals(1, realmTypes.getColumnUUIDList().size()); assertEquals(UUID.fromString(TestHelper.generateUUIDString(5)), realmTypes.getColumnUUIDList().get(0)); assertEquals(1, realmTypes.getColumnRealmAnyList().size()); assertEquals(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(7))), realmTypes.getColumnRealmAnyList().get(0)); } @Test public void copyToRealm_cyclicObjectReferences() { CyclicType oneCyclicType = new CyclicType(); oneCyclicType.setName(""One""); CyclicType anotherCyclicType = new CyclicType(); anotherCyclicType.setName(""Two""); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); CyclicType realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals(""One"", realmObject.getName()); assertEquals(""Two"", realmObject.getObject().getName()); assertEquals(2, realm.where(CyclicType.class).count()); // Tests copyToRealm overload that uses the Iterator. // Makes sure we reuse the same graph cache Map to avoid duplicates. realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(CyclicType.class).count()); realm.beginTransaction(); List cyclicTypes = realm.copyToRealm(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); assertEquals(2, cyclicTypes.size()); assertEquals(""One"", cyclicTypes.get(0).getName()); assertEquals(""Two"", cyclicTypes.get(1).getName()); assertEquals(2, realm.where(CyclicType.class).count()); } @Test public void copyToRealm_cyclicObjectReferencesWithPK() { CyclicTypePrimaryKey oneCyclicType = new CyclicTypePrimaryKey(1, ""One""); CyclicTypePrimaryKey anotherCyclicType = new CyclicTypePrimaryKey(2, ""Two""); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); CyclicTypePrimaryKey realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals(""One"", realmObject.getName()); assertEquals(""Two"", realmObject.getObject().getName()); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); // Tests copyToRealm overload that uses the Iterator. // Makes sure we reuse the same graph cache Map to avoid duplicates. realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(CyclicTypePrimaryKey.class).count()); realm.beginTransaction(); List cyclicTypes = realm.copyToRealm(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); assertEquals(2, cyclicTypes.size()); assertEquals(""One"", cyclicTypes.get(0).getName()); assertEquals(""Two"", cyclicTypes.get(1).getName()); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); } @Test public void copyToRealm_cyclicListReferences() { CyclicType oneCyclicType = new CyclicType(); oneCyclicType.setName(""One""); CyclicType anotherCyclicType = new CyclicType(); anotherCyclicType.setName(""Two""); oneCyclicType.setObjects(new RealmList<>(anotherCyclicType)); anotherCyclicType.setObjects(new RealmList<>(oneCyclicType)); realm.beginTransaction(); CyclicType realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals(""One"", realmObject.getName()); assertEquals(2, realm.where(CyclicType.class).count()); } // Checks that if a field has a null value, it gets converted to the default value for that type. @Test public void copyToRealm_convertsNullToDefaultValue() { realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(new AllTypes()); realm.commitTransaction(); assertEquals("""", realmTypes.getColumnString()); assertEquals(new Date(0), realmTypes.getColumnDate()); assertArrayEquals(new byte[0], realmTypes.getColumnBinary()); assertNotNull(realmTypes.getColumnRealmList()); assertNotNull(realmTypes.getColumnStringList()); assertNotNull(realmTypes.getColumnBinaryList()); assertNotNull(realmTypes.getColumnBooleanList()); assertNotNull(realmTypes.getColumnLongList()); assertNotNull(realmTypes.getColumnDoubleList()); assertNotNull(realmTypes.getColumnFloatList()); assertNotNull(realmTypes.getColumnDateList()); } // Check that using copyToRealm will set the primary key directly instead of first setting // it to the default value (which can fail). @Test public void copyToRealm_primaryKeyIsSetDirectly() { realm.beginTransaction(); realm.createObject(OwnerPrimaryKey.class, 0); realm.copyToRealm(new OwnerPrimaryKey(1, ""Foo"")); realm.commitTransaction(); assertEquals(2, realm.where(OwnerPrimaryKey.class).count()); } @Test public void copyToRealm_stringPrimaryKeyIsNull() { final long SECONDARY_FIELD_VALUE = 34992142L; TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, SECONDARY_FIELD_VALUE); RealmResults results = realm.where(PrimaryKeyAsString.class).findAll(); assertEquals(1, results.size()); assertEquals(null, results.first().getName()); assertEquals(SECONDARY_FIELD_VALUE, results.first().getId()); } @Test public void copyToRealm_boxedNumberPrimaryKeyIsNull() { final String SECONDARY_FIELD_VALUE = ""nullNumberPrimaryKeyObj""; final Class[] CLASSES = {PrimaryKeyAsBoxedByte.class, PrimaryKeyAsBoxedShort.class, PrimaryKeyAsBoxedInteger.class, PrimaryKeyAsBoxedLong.class}; TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, SECONDARY_FIELD_VALUE); TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); for (Class clazz : CLASSES) { RealmResults results = realm.where(clazz).findAll(); assertEquals(1, results.size()); assertEquals(null, ((NullPrimaryKey) results.first()).getId()); assertEquals(SECONDARY_FIELD_VALUE, ((NullPrimaryKey) results.first()).getName()); } } @Test public void copyToRealm_duplicatedPrimaryKeyThrows() { final String[] PRIMARY_KEY_TYPES = { ""String"", ""BoxedLong"", ""long"" }; for (String className : PRIMARY_KEY_TYPES) { String expectedKey = null; try { realm.beginTransaction(); switch (className) { case ""String"": { expectedKey = ""foo""; PrimaryKeyAsString obj = new PrimaryKeyAsString(""foo""); realm.copyToRealm(obj); realm.copyToRealm(obj); break; } case ""BoxedLong"": { expectedKey = Long.toString(Long.MIN_VALUE); PrimaryKeyAsBoxedLong obj = new PrimaryKeyAsBoxedLong(Long.MIN_VALUE, ""boxedlong""); realm.copyToRealm(obj); realm.copyToRealm(obj); break; } case ""long"": expectedKey = Long.toString(Long.MAX_VALUE); PrimaryKeyAsLong obj = new PrimaryKeyAsLong(Long.MAX_VALUE); realm.copyToRealm(obj); realm.copyToRealm(obj); break; default: } fail(""Null value as primary key already exists, but wasn't detected correctly""); } catch (RealmPrimaryKeyConstraintException expected) { assertTrue(""Exception message is: "" + expected.getMessage(), expected.getMessage().contains(""with an existing primary key value '""+ expectedKey +""'"")); } finally { realm.cancelTransaction(); } } } @Test public void copyToRealm_duplicatedNullPrimaryKeyThrows() { final String[] PRIMARY_KEY_TYPES = {""String"", ""BoxedByte"", ""BoxedShort"", ""BoxedInteger"", ""BoxedLong""}; TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, 0); TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, (String) null); TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, (String) null); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, (String) null); TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, (String) null); for (String className : PRIMARY_KEY_TYPES) { try { realm.beginTransaction(); switch (className) { case ""String"": realm.copyToRealm(new PrimaryKeyAsString()); break; case ""BoxedByte"": realm.copyToRealm(new PrimaryKeyAsBoxedByte()); break; case ""BoxedShort"": realm.copyToRealm(new PrimaryKeyAsBoxedShort()); break; case ""BoxedInteger"": realm.copyToRealm(new PrimaryKeyAsBoxedInteger()); break; case ""BoxedLong"": realm.copyToRealm(new PrimaryKeyAsBoxedLong()); break; default: } fail(""Null value as primary key already exists, but wasn't detected correctly""); } catch (RealmPrimaryKeyConstraintException expected) { assertTrue(""Exception message is: "" + expected.getMessage(), expected.getMessage().contains(""with an existing primary key value 'null'"")); } finally { realm.cancelTransaction(); } } } @Test public void copyToRealm_doNotCopyReferencedObjectIfManaged() { realm.beginTransaction(); // Child object is managed by Realm. CyclicTypePrimaryKey childObj = realm.createObject(CyclicTypePrimaryKey.class, 1); childObj.setName(""Child""); // Parent object is an unmanaged object. CyclicTypePrimaryKey parentObj = new CyclicTypePrimaryKey(2); parentObj.setObject(childObj); realm.copyToRealm(parentObj); realm.commitTransaction(); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); } @Test public void copyToRealm_list() { Dog dog1 = new Dog(); dog1.setName(""Dog 1""); Dog dog2 = new Dog(); dog2.setName(""Dog 2""); RealmList list = new RealmList(); list.addAll(Arrays.asList(dog1, dog2)); realm.beginTransaction(); List copiedList = new ArrayList(realm.copyToRealm(list)); realm.commitTransaction(); assertEquals(2, copiedList.size()); assertEquals(dog1.getName(), copiedList.get(0).getName()); assertEquals(dog2.getName(), copiedList.get(1).getName()); } @Test public void copyToRealm_objectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); realm.beginTransaction(); final Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(dog); fail(); } catch (IllegalArgumentException expected) { assertEquals(""Objects which belong to Realm instances in other threads cannot be copied into this"" + "" Realm instance."", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } @Test public void copyToRealmOrUpdate_null() { realm.beginTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyToRealmOrUpdate((AllTypes) null); } @Test public void copyToRealmOrUpdate_stringPrimaryKeyFieldIsNull() { final long SECONDARY_FIELD_VALUE = 2192841L; final long SECONDARY_FIELD_UPDATED = 44887612L; PrimaryKeyAsString nullPrimaryKeyObj = TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsString.class).findAll(); assertEquals(1, result.size()); assertEquals(null, result.first().getName()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setId(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsString.class).findFirst().getId()); } @Test public void copyToRealmOrUpdate_boxedBytePrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullBytePrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullBytePrimaryKeyObjUpdated""; PrimaryKeyAsBoxedByte nullPrimaryKeyObj = TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedByte.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedByte.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedShortPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullShortPrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullShortPrimaryKeyObjUpdated""; PrimaryKeyAsBoxedShort nullPrimaryKeyObj = TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedShort.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedShort.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedIntegerPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullIntegerPrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullIntegerPrimaryKeyObjUpdated""; PrimaryKeyAsBoxedInteger nullPrimaryKeyObj = TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedInteger.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedInteger.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedLongPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullLongPrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullLongPrimaryKeyObjUpdated""; PrimaryKeyAsBoxedLong nullPrimaryKeyObj = TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedLong.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedLong.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_noPrimaryKeyField() { realm.beginTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyToRealmOrUpdate(new AllTypes()); } @Test public void copyToRealmOrUpdate_addNewObjects() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); realm.copyToRealm(obj); PrimaryKeyAsLong obj2 = new PrimaryKeyAsLong(); obj2.setId(2); obj2.setName(""Bar""); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(2, realm.where(PrimaryKeyAsLong.class).count()); } @Test public void copyToRealmOrUpdate_updateExistingObject() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnString(""Foo""); obj.setColumnLong(1); obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, ""Dog1"")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, ""Dog2""))); obj.setColumnBoxedBoolean(true); obj.setColumnStringList(new RealmList<>(""1"")); obj.setColumnBooleanList(new RealmList<>(false)); obj.setColumnBinaryList(new RealmList<>(new byte[] {1})); obj.setColumnLongList(new RealmList<>(1L)); obj.setColumnDoubleList(new RealmList<>(1D)); obj.setColumnFloatList(new RealmList<>(1F)); obj.setColumnDateList(new RealmList<>(new Date(1L))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnString(""Bar""); obj2.setColumnLong(1); obj2.setColumnFloat(2.23F); obj2.setColumnDouble(2.234D); obj2.setColumnBoolean(true); obj2.setColumnBinary(new byte[] {2, 3, 4}); obj2.setColumnDate(new Date(2000)); obj2.setColumnRealmObject(new DogPrimaryKey(3, ""Dog3"")); obj2.setColumnRealmList(new RealmList(new DogPrimaryKey(4, ""Dog4""))); obj2.setColumnBoxedBoolean(false); obj2.setColumnStringList(new RealmList<>(""2"", ""3"")); obj2.setColumnBooleanList(new RealmList<>(true, false)); obj2.setColumnBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); obj2.setColumnLongList(new RealmList<>(2L, 3L)); obj2.setColumnDoubleList(new RealmList<>(2D, 3D)); obj2.setColumnFloatList(new RealmList<>(2F, 3F)); obj2.setColumnDateList(new RealmList<>(new Date(2L), new Date(3L))); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst(); // Checks that the the only element has all its properties updated. assertEquals(""Bar"", obj.getColumnString()); assertEquals(1, obj.getColumnLong()); assertEquals(2.23F, obj.getColumnFloat(), 0); assertEquals(2.234D, obj.getColumnDouble(), 0); assertEquals(true, obj.isColumnBoolean()); assertArrayEquals(new byte[] {2, 3, 4}, obj.getColumnBinary()); assertEquals(new Date(2000), obj.getColumnDate()); assertEquals(""Dog3"", obj.getColumnRealmObject().getName()); assertEquals(1, obj.getColumnRealmList().size()); assertEquals(""Dog4"", obj.getColumnRealmList().get(0).getName()); assertFalse(obj.getColumnBoxedBoolean()); assertEquals(2, obj.getColumnStringList().size()); assertEquals(""2"", obj.getColumnStringList().get(0)); assertEquals(""3"", obj.getColumnStringList().get(1)); assertEquals(2, obj.getColumnBooleanList().size()); assertEquals(true, obj.getColumnBooleanList().get(0)); assertEquals(false, obj.getColumnBooleanList().get(1)); assertEquals(2, obj.getColumnBinaryList().size()); assertArrayEquals(new byte[] {2}, obj.getColumnBinaryList().get(0)); assertArrayEquals(new byte[] {3}, obj.getColumnBinaryList().get(1)); assertEquals(2, obj.getColumnLongList().size()); assertEquals((Long) 2L, obj.getColumnLongList().get(0)); assertEquals((Long) 3L, obj.getColumnLongList().get(1)); assertEquals(2, obj.getColumnDoubleList().size()); assertEquals((Double) 2D, obj.getColumnDoubleList().get(0)); assertEquals((Double) 3D, obj.getColumnDoubleList().get(1)); assertEquals(2, obj.getColumnFloatList().size()); assertEquals((Float) 2F, obj.getColumnFloatList().get(0)); assertEquals((Float) 3F, obj.getColumnFloatList().get(1)); assertEquals(2, obj.getColumnDateList().size()); assertEquals(new Date(2L), obj.getColumnDateList().get(0)); assertEquals(new Date(3L), obj.getColumnDateList().get(1)); } @Test public void copyToRealmOrUpdate_overrideOwnList() { realm.beginTransaction(); AllJavaTypes managedObj = realm.createObject(AllJavaTypes.class, 1); managedObj.getFieldList().add(managedObj); AllJavaTypes unmanagedObj = realm.copyFromRealm(managedObj); unmanagedObj.setFieldList(managedObj.getFieldList()); managedObj = realm.copyToRealmOrUpdate(unmanagedObj); assertEquals(1, managedObj.getFieldList().size()); assertEquals(1, managedObj.getFieldList().first().getFieldId()); } @Test public void copyToRealmOrUpdate_cyclicObject() { CyclicTypePrimaryKey oneCyclicType = new CyclicTypePrimaryKey(1); oneCyclicType.setName(""One""); CyclicTypePrimaryKey anotherCyclicType = new CyclicTypePrimaryKey(2); anotherCyclicType.setName(""Two""); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); realm.copyToRealm(oneCyclicType); realm.commitTransaction(); oneCyclicType.setName(""Three""); anotherCyclicType.setName(""Four""); realm.beginTransaction(); realm.copyToRealmOrUpdate(oneCyclicType); realm.commitTransaction(); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); assertEquals(""Three"", realm.where(CyclicTypePrimaryKey.class).equalTo(""id"", 1).findFirst().getName()); } // Checks that an unmanaged object with only default values can override data. @Test public void copyToRealmOrUpdate_defaultValuesOverrideExistingData() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnString(""Foo""); obj.setColumnLong(1); obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, ""Dog1"")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, ""Dog2""))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnLong(1); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst(); assertNull(obj.getColumnString()); assertEquals(1, obj.getColumnLong()); assertEquals(0.0F, obj.getColumnFloat(), 0); assertEquals(0.0D, obj.getColumnDouble(), 0); assertEquals(false, obj.isColumnBoolean()); assertNull(obj.getColumnBinary()); assertNull(obj.getColumnDate()); assertNull(obj.getColumnRealmObject()); assertEquals(0, obj.getColumnRealmList().size()); } // Tests that if references to objects are removed, the objects are still in the Realm. @Test public void copyToRealmOrUpdate_referencesNotDeleted() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnLong(1); obj.setColumnRealmObject(new DogPrimaryKey(1, ""Dog1"")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, ""Dog2""))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnLong(1); obj2.setColumnRealmObject(new DogPrimaryKey(3, ""Dog3"")); obj2.setColumnRealmList(new RealmList(new DogPrimaryKey(4, ""Dog4""))); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); assertEquals(4, realm.where(DogPrimaryKey.class).count()); } @Test public void copyToRealmOrUpdate_primaryKeyMixInObjectGraph() { // Crate Object graph where tier 2 consists of 1 object with primary key and one doesn't. // Tier 3 both have objects with primary keys. // // PK // / \ // PK nonPK // | | // PK PK DogPrimaryKey dog = new DogPrimaryKey(1, ""Dog""); OwnerPrimaryKey owner = new OwnerPrimaryKey(1, ""Owner""); owner.setDog(dog); Cat cat = new Cat(); cat.setScaredOfDog(dog); PrimaryKeyMix mixObject = new PrimaryKeyMix(1); mixObject.setDogOwner(owner); mixObject.setCat(cat); realm.beginTransaction(); PrimaryKeyMix realmObject = realm.copyToRealmOrUpdate(mixObject); realm.commitTransaction(); assertEquals(""Dog"", realmObject.getCat().getScaredOfDog().getName()); assertEquals(""Dog"", realmObject.getDogOwner().getDog().getName()); } @Test public void copyToRealmOrUpdate_iterable() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); realm.copyToRealm(obj); PrimaryKeyAsLong obj2 = new PrimaryKeyAsLong(); obj2.setId(1); obj2.setName(""Bar""); PrimaryKeyAsLong obj3 = new PrimaryKeyAsLong(); obj3.setId(1); obj3.setName(""Baz""); realm.copyToRealmOrUpdate(Arrays.asList(obj2, obj3)); } }); assertEquals(1, realm.where(PrimaryKeyAsLong.class).count()); assertEquals(""Baz"", realm.where(PrimaryKeyAsLong.class).findFirst().getName()); } // Tests that a collection of objects with references all gets copied. @Test public void copyToRealmOrUpdate_iterableChildObjects() { DogPrimaryKey dog = new DogPrimaryKey(1, ""Snoop""); AllTypesPrimaryKey allTypes1 = new AllTypesPrimaryKey(); allTypes1.setColumnLong(1); allTypes1.setColumnRealmObject(dog); AllTypesPrimaryKey allTypes2 = new AllTypesPrimaryKey(); allTypes1.setColumnLong(2); allTypes2.setColumnRealmObject(dog); realm.beginTransaction(); realm.copyToRealmOrUpdate(Arrays.asList(allTypes1, allTypes2)); realm.commitTransaction(); assertEquals(2, realm.where(AllTypesPrimaryKey.class).count()); assertEquals(1, realm.where(DogPrimaryKey.class).count()); } @Test public void copyToRealmOrUpdate_objectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); realm.beginTransaction(); final OwnerPrimaryKey ownerPrimaryKey = realm.createObject(OwnerPrimaryKey.class, 0); realm.commitTransaction(); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(ownerPrimaryKey); fail(); } catch (IllegalArgumentException expected) { assertEquals(""Objects which belong to Realm instances in other threads cannot be copied into this"" + "" Realm instance."", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } @Test public void copyToRealmOrUpdate_listHasObjectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); final OwnerPrimaryKey ownerPrimaryKey = new OwnerPrimaryKey(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); ownerPrimaryKey.setDogs(new RealmList(dog)); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(ownerPrimaryKey); fail(); } catch (IllegalArgumentException expected) { assertEquals(""Objects which belong to Realm instances in other threads cannot be copied into this"" + "" Realm instance."", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } // Test to reproduce issue https://github.com/realm/realm-java/issues/4957 @Test public void copyToRealmOrUpdate_bug4957() { Object4957 listElement = new Object4957(); listElement.setId(1); Object4957 parent = new Object4957(); parent.setId(0); parent.getChildList().add(listElement); // parentCopy has same fields as the parent does. But they are not the same object. Object4957 parentCopy = new Object4957(); parentCopy.setId(0); parentCopy.getChildList().add(listElement); parent.setChild(parentCopy); parentCopy.setChild(parentCopy); realm.beginTransaction(); Object4957 managedParent = realm.copyToRealmOrUpdate(parent); realm.commitTransaction(); // The original bug fails here. It resulted the listElement has been added to the list twice. // Because of the parent and parentCopy are not the same object, proxy will miss the cache to know the object // has been created before. But it does know they share the same PK value. assertEquals(1, managedParent.getChildList().size()); // insertOrUpdate doesn't have the problem! realm.beginTransaction(); realm.deleteAll(); realm.insertOrUpdate(parent); realm.commitTransaction(); managedParent = realm.where(Object4957.class).findFirst(); assertEquals(1, managedParent.getChildList().size()); } @Test public void getInstance_differentEncryptionKeys() { byte[] key1 = TestHelper.getRandomKey(42); byte[] key2 = TestHelper.getRandomKey(42); // Makes sure the key is the same, but in two different instances. assertArrayEquals(key1, key2); assertTrue(key1 != key2); final String ENCRYPTED_REALM = ""differentKeys.realm""; Realm realm1 = null; Realm realm2 = null; try { realm1 = Realm.getInstance(configFactory.createConfiguration(ENCRYPTED_REALM, key1)); try { realm2 = Realm.getInstance(configFactory.createConfiguration(ENCRYPTED_REALM, key2)); } catch (Exception e) { fail(""Unexpected exception: "" + e); } finally { if (realm2 != null) { realm2.close(); } } } finally { if (realm1 != null) { realm1.close(); } } } @Test public void writeEncryptedCopyTo() throws Exception { populateTestRealm(); long before = realm.where(AllTypes.class).count(); assertEquals(TEST_DATA_SIZE, before); // Configures test realms. final String ENCRYPTED_REALM_FILE_NAME = ""encryptedTestRealm.realm""; final String RE_ENCRYPTED_REALM_FILE_NAME = ""reEncryptedTestRealm.realm""; final String DECRYPTED_REALM_FILE_NAME = ""decryptedTestRealm.realm""; RealmConfiguration encryptedRealmConfig = configFactory.createConfiguration(ENCRYPTED_REALM_FILE_NAME, TestHelper.getRandomKey()); RealmConfiguration reEncryptedRealmConfig = configFactory.createConfiguration(RE_ENCRYPTED_REALM_FILE_NAME, TestHelper.getRandomKey()); RealmConfiguration decryptedRealmConfig = configFactory.createConfiguration(DECRYPTED_REALM_FILE_NAME); // Writes encrypted copy from a unencrypted Realm. File destination = new File(encryptedRealmConfig.getPath()); realm.writeEncryptedCopyTo(destination, encryptedRealmConfig.getEncryptionKey()); Realm encryptedRealm = null; try { // Verifies encrypted Realm and writes new encrypted copy with a new key. encryptedRealm = Realm.getInstance(encryptedRealmConfig); assertEquals(TEST_DATA_SIZE, encryptedRealm.where(AllTypes.class).count()); destination = new File(reEncryptedRealmConfig.getPath()); encryptedRealm.writeEncryptedCopyTo(destination, reEncryptedRealmConfig.getEncryptionKey()); // Verifies re-encrypted copy. Realm reEncryptedRealm = null; try { reEncryptedRealm = Realm.getInstance(reEncryptedRealmConfig); assertEquals(TEST_DATA_SIZE, reEncryptedRealm.where(AllTypes.class).count()); } finally { if (reEncryptedRealm != null) { reEncryptedRealm.close(); if (!Realm.deleteRealm(reEncryptedRealmConfig)) { fail(); } } } // Writes non-encrypted copy from the encrypted version. destination = new File(decryptedRealmConfig.getPath()); encryptedRealm.writeEncryptedCopyTo(destination, null); // Verifies decrypted Realm and cleans up. Realm decryptedRealm = null; try { decryptedRealm = Realm.getInstance(decryptedRealmConfig); assertEquals(TEST_DATA_SIZE, decryptedRealm.where(AllTypes.class).count()); } finally { if (decryptedRealm != null) { decryptedRealm.close(); if (!Realm.deleteRealm(decryptedRealmConfig)) { fail(); } } } } finally { if (encryptedRealm != null) { encryptedRealm.close(); if (!Realm.deleteRealm(encryptedRealmConfig)) { fail(); } } } } @Test public void writeEncryptedCopyTo_wrongKeyLength() { byte[] wrongLengthKey = new byte[42]; File destination = new File(configFactory.getRoot(), ""wrong_key.realm""); thrown.expect(IllegalArgumentException.class); realm.writeEncryptedCopyTo(destination, wrongLengthKey); } @Test public void deleteRealm_failures() { final String OTHER_REALM_NAME = ""yetAnotherRealm.realm""; RealmConfiguration configA = configFactory.createConfiguration(); RealmConfiguration configB = configFactory.createConfiguration(OTHER_REALM_NAME); // This instance is already cached because of the setUp() method so this deletion should throw. try { Realm.deleteRealm(configA); fail(); } catch (IllegalStateException ignored) { } // Creates a new Realm file. Realm yetAnotherRealm = Realm.getInstance(configB); // Deleting it should fail. try { Realm.deleteRealm(configB); fail(); } catch (IllegalStateException ignored) { } // But now that we close it deletion should work. yetAnotherRealm.close(); try { Realm.deleteRealm(configB); } catch (Exception e) { fail(); } } // TODO Does this test something meaningfull not tested elsewhere? @Test public void setter_updateField() throws Exception { realm.beginTransaction(); // Creates an owner with two dogs. OwnerPrimaryKey owner = realm.createObject(OwnerPrimaryKey.class, 1); owner.setName(""Jack""); Dog rex = realm.createObject(Dog.class); rex.setName(""Rex""); Dog fido = realm.createObject(Dog.class); fido.setName(""Fido""); owner.getDogs().add(rex); owner.getDogs().add(fido); assertEquals(2, owner.getDogs().size()); // Changing the name of the owner should not affect the number of dogs. owner.setName(""Peter""); assertEquals(2, owner.getDogs().size()); // Updating the user should not affect it either. This is actually a no-op since owner is a Realm backed object. OwnerPrimaryKey owner2 = realm.copyToRealmOrUpdate(owner); assertEquals(2, owner.getDogs().size()); assertEquals(2, owner2.getDogs().size()); realm.commitTransaction(); } @Test public void deleteRealm() throws InterruptedException { File tempDir = new File(configFactory.getRoot(), ""delete_test_dir""); File tempDirRenamed = new File(configFactory.getRoot(), ""delete_test_dir_2""); assertTrue(tempDir.mkdir()); final RealmConfiguration configuration = configFactory.createConfigurationBuilder() .directory(tempDir) .build(); final CountDownLatch bgThreadReadyLatch = new CountDownLatch(1); final CountDownLatch readyToCloseLatch = new CountDownLatch(1); final CountDownLatch closedLatch = new CountDownLatch(1); Realm realm = Realm.getInstance(configuration); // Creates another Realm to ensure the log files are generated. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(configuration); bgThreadReadyLatch.countDown(); TestHelper.awaitOrFail(readyToCloseLatch); realm.close(); closedLatch.countDown(); } }).start(); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); // Waits for bg thread's opening the same Realm. TestHelper.awaitOrFail(bgThreadReadyLatch); // A core upgrade might change the location of the files assertTrue(tempDir.renameTo(tempDirRenamed)); readyToCloseLatch.countDown(); realm.close(); TestHelper.awaitOrFail(closedLatch); // Now we get log files back! assertTrue(tempDirRenamed.renameTo(tempDir)); assertTrue(Realm.deleteRealm(configuration)); assertEquals(1, tempDir.listFiles().length); // Lock file should never be deleted File lockFile = new File(configuration.getPath() + "".lock""); assertTrue(lockFile.exists()); } // Tests that all methods that require a transaction. (ie. any function that mutates Realm data) @Test public void callMutableMethodOutsideTransaction() throws JSONException, IOException { // Prepares unmanaged object data. AllTypesPrimaryKey t = new AllTypesPrimaryKey(); List ts = Arrays.asList(t, t); // Prepares JSON data. String jsonObjStr = ""{ \""columnLong\"" : 1 }""; JSONObject jsonObj = new JSONObject(jsonObjStr); InputStream jsonObjStream = TestHelper.stringToStream(jsonObjStr); InputStream jsonObjStream2 = TestHelper.stringToStream(jsonObjStr); String jsonArrStr = "" [{ \""columnLong\"" : 1 }] ""; JSONArray jsonArr = new JSONArray(jsonArrStr); InputStream jsonArrStream = TestHelper.stringToStream(jsonArrStr); InputStream jsonArrStream2 = TestHelper.stringToStream(jsonArrStr); // Tests all methods that should require a transaction. try { realm.createObject(AllTypes.class); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealm(t); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealm(ts); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealmOrUpdate(t); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealmOrUpdate(ts); fail(); } catch (IllegalStateException expected) {} try { realm.delete(AllTypes.class); fail(); } catch (IllegalStateException expected) {} try { realm.deleteAll(); fail(); } catch (IllegalStateException expected) {} try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, jsonObjStream); fail(); } catch (IllegalStateException expected) {} } try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2); fail(); } catch (IllegalStateException expected) {} } try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream); fail(); } catch (IllegalStateException expected) {} } try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2); fail(); } catch (IllegalStateException expected) {} } } @Test public void createObject_cannotCreateDynamicRealmObject() { realm.beginTransaction(); try { realm.createObject(DynamicRealmObject.class); fail(); } catch (RealmException ignored) { } } @Test(expected = RealmException.class) public void createObject_absentPrimaryKeyThrows() { realm.beginTransaction(); realm.createObject(DogPrimaryKey.class); } @Test public void createObjectWithPrimaryKey() { realm.beginTransaction(); AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); assertEquals(1, realm.where(AllJavaTypes.class).count()); assertEquals(42, obj.getFieldId()); } @Test public void createObjectWithPrimaryKey_noPrimaryKeyField() { realm.beginTransaction(); try { realm.createObject(AllTypes.class, 42); fail(); } catch (IllegalStateException ignored) { } } @Test public void createObjectWithPrimaryKey_wrongValueType() { realm.beginTransaction(); try { realm.createObject(AllJavaTypes.class, ""fortyTwo""); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void createObjectWithPrimaryKey_valueAlreadyExists() { realm.beginTransaction(); realm.createObject(AllJavaTypes.class, 42); try { realm.createObject(AllJavaTypes.class, 42); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } } @Test public void createObjectWithPrimaryKey_null() { // Byte realm.beginTransaction(); PrimaryKeyAsBoxedByte primaryKeyAsBoxedByte = realm.createObject(PrimaryKeyAsBoxedByte.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedByte.class).count()); assertNull(primaryKeyAsBoxedByte.getId()); // Short realm.beginTransaction(); PrimaryKeyAsBoxedShort primaryKeyAsBoxedShort = realm.createObject(PrimaryKeyAsBoxedShort.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedShort.class).count()); assertNull(primaryKeyAsBoxedShort.getId()); // Integer realm.beginTransaction(); PrimaryKeyAsBoxedInteger primaryKeyAsBoxedInteger = realm.createObject(PrimaryKeyAsBoxedInteger.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedInteger.class).count()); assertNull(primaryKeyAsBoxedInteger.getId()); // Long realm.beginTransaction(); PrimaryKeyAsBoxedLong primaryKeyAsBoxedLong = realm.createObject(PrimaryKeyAsBoxedLong.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedLong.class).count()); assertNull(primaryKeyAsBoxedLong.getId()); // String realm.beginTransaction(); PrimaryKeyAsString primaryKeyAsString = realm.createObject(PrimaryKeyAsString.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsString.class).count()); assertNull(primaryKeyAsString.getName()); } @Test public void createObjectWithPrimaryKey_nullOnRequired() { realm.beginTransaction(); // Byte try { realm.createObject(PrimaryKeyRequiredAsBoxedByte.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Short try { realm.createObject(PrimaryKeyRequiredAsBoxedShort.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Integer try { realm.createObject(PrimaryKeyRequiredAsBoxedInteger.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Long try { realm.createObject(PrimaryKeyRequiredAsBoxedLong.class, null); fail(); } catch (IllegalArgumentException ignored) { } // String try { realm.createObject(PrimaryKeyRequiredAsString.class, null); fail(); } catch (IllegalArgumentException ignored) { } realm.cancelTransaction(); } @Test public void createObjectWithPrimaryKey_nullDuplicated() { realm.beginTransaction(); // Byte realm.createObject(PrimaryKeyAsBoxedByte.class, null); try { realm.createObject(PrimaryKeyAsBoxedByte.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Short realm.createObject(PrimaryKeyAsBoxedShort.class, null); try { realm.createObject(PrimaryKeyAsBoxedShort.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Integer realm.createObject(PrimaryKeyAsBoxedInteger.class, null); try { realm.createObject(PrimaryKeyAsBoxedInteger.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Long realm.createObject(PrimaryKeyAsBoxedLong.class, null); try { realm.createObject(PrimaryKeyAsBoxedLong.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // String realm.createObject(PrimaryKeyAsString.class, null); try { realm.createObject(PrimaryKeyAsString.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } realm.cancelTransaction(); } @Test public void createObject_defaultValueFromModelField() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueOfField with non-default primary key value. realm.createObject(DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueOfField.lastRandomStringValue; testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_STRING, DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_SHORT, DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_INT, DefaultValueOfField.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG, DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BYTE, DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_FLOAT, DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_DOUBLE, DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BOOLEAN, DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_DATE, DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BINARY, DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_OBJECT + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } @Test public void createObject_overwriteNullifiedLinkWithDefaultValue() { final DefaultValueOverwriteNullLink created; realm.beginTransaction(); created = realm.createObject(DefaultValueOverwriteNullLink.class); realm.commitTransaction(); assertEquals(created.getExpectedKeyOfFieldObject(), created.getFieldObject().getFieldRandomPrimaryKey()); } @Test public void createObject_defaultValueFromModelConstructor() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueConstructor with non-default primary key value. realm.createObject(DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueConstructor.lastRandomStringValue; testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_STRING, DefaultValueConstructor.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_SHORT, DefaultValueConstructor.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_INT, DefaultValueConstructor.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG, DefaultValueConstructor.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BYTE, DefaultValueConstructor.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_FLOAT, DefaultValueConstructor.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_DOUBLE, DefaultValueConstructor.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BOOLEAN, DefaultValueConstructor.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_DATE, DefaultValueConstructor.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BINARY, DefaultValueConstructor.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_OBJECT + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } @Test public void createObject_defaultValueSetterInConstructor() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueSetter with non-default primary key value. realm.createObject(DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueSetter.lastRandomStringValue; testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_STRING, DefaultValueSetter.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_SHORT, DefaultValueSetter.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_INT, DefaultValueSetter.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG, DefaultValueSetter.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BYTE, DefaultValueSetter.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_FLOAT, DefaultValueSetter.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_DOUBLE, DefaultValueSetter.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BOOLEAN, DefaultValueSetter.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_DATE, DefaultValueSetter.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BINARY, DefaultValueSetter.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_OBJECT + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); } @Test public void createObject_defaultValueFromOtherConstructor() { realm.beginTransaction(); DefaultValueFromOtherConstructor obj = realm.createObject(DefaultValueFromOtherConstructor.class); realm.commitTransaction(); assertEquals(42, obj.getFieldLong()); } @Test public void copyToRealm_defaultValuesAreIgnored() { final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + "".modified""; final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + "".modified""; final String fieldRandomStringValue = ""non-random""; final short fieldShortValue = (short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1); final int fieldIntValue = DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1; final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; final long fieldLongValue = DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1; final byte fieldByteValue = (byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1); final float fieldFloatValue = DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1; final double fieldDoubleValue = DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1; final boolean fieldBooleanValue = !DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE; final Date fieldDateValue = new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1); final byte[] fieldBinaryValue = {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}; final int fieldObjectIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1; final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; final DefaultValueOfField managedObj; realm.beginTransaction(); { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(fieldIgnoredValue); obj.setFieldString(fieldStringValue); obj.setFieldRandomString(fieldRandomStringValue); obj.setFieldShort(fieldShortValue); obj.setFieldInt(fieldIntValue); obj.setFieldLongPrimaryKey(fieldLongPrimaryKeyValue); obj.setFieldLong(fieldLongValue); obj.setFieldByte(fieldByteValue); obj.setFieldFloat(fieldFloatValue); obj.setFieldDouble(fieldDoubleValue); obj.setFieldBoolean(fieldBooleanValue); obj.setFieldDate(fieldDateValue); obj.setFieldBinary(fieldBinaryValue); final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); fieldObjectValue.setFieldInt(fieldObjectIntValue); obj.setFieldObject(fieldObjectValue); final RealmList list = new RealmList(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(fieldListIntValue); list.add(listItem); obj.setFieldList(list); obj.setFieldStringList(new RealmList<>(""2"", ""3"")); obj.setFieldBooleanList(new RealmList<>(true, false)); obj.setFieldBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); obj.setFieldLongList(new RealmList<>(2L, 3L)); obj.setFieldIntegerList(new RealmList<>(2, 3)); obj.setFieldShortList(new RealmList<>((short) 2, (short) 3)); obj.setFieldByteList(new RealmList<>((byte) 2, (byte) 3)); obj.setFieldDoubleList(new RealmList<>(2D, 3D)); obj.setFieldFloatList(new RealmList<>(2F, 3F)); obj.setFieldDateList(new RealmList<>(new Date(2L), new Date(3L))); managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/, managedObj.getFieldIgnored()); assertEquals(fieldStringValue, managedObj.getFieldString()); assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString()); assertEquals(fieldShortValue, managedObj.getFieldShort()); assertEquals(fieldIntValue, managedObj.getFieldInt()); assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); assertEquals(fieldLongValue, managedObj.getFieldLong()); assertEquals(fieldByteValue, managedObj.getFieldByte()); assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0F); assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0D); assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); assertEquals(fieldDateValue, managedObj.getFieldDate()); assertArrayEquals(fieldBinaryValue, managedObj.getFieldBinary()); assertEquals(fieldObjectIntValue, managedObj.getFieldObject().getFieldInt()); assertEquals(1, managedObj.getFieldList().size()); assertEquals(fieldListIntValue, managedObj.getFieldList().first().getFieldInt()); assertEquals(2, managedObj.getFieldStringList().size()); assertEquals(""2"", managedObj.getFieldStringList().get(0)); assertEquals(""3"", managedObj.getFieldStringList().get(1)); assertEquals(2, managedObj.getFieldBooleanList().size()); assertEquals(true, managedObj.getFieldBooleanList().get(0)); assertEquals(false, managedObj.getFieldBooleanList().get(1)); assertEquals(2, managedObj.getFieldBinaryList().size()); assertArrayEquals(new byte[] {2}, managedObj.getFieldBinaryList().get(0)); assertArrayEquals(new byte[] {3}, managedObj.getFieldBinaryList().get(1)); assertEquals(2, managedObj.getFieldLongList().size()); assertEquals((Long) 2L, managedObj.getFieldLongList().get(0)); assertEquals((Long) 3L, managedObj.getFieldLongList().get(1)); assertEquals(2, managedObj.getFieldIntegerList().size()); assertEquals((Integer) 2, managedObj.getFieldIntegerList().get(0)); assertEquals((Integer) 3, managedObj.getFieldIntegerList().get(1)); assertEquals(2, managedObj.getFieldShortList().size()); assertEquals((Short) (short) 2, managedObj.getFieldShortList().get(0)); assertEquals((Short) (short) 3, managedObj.getFieldShortList().get(1)); assertEquals(2, managedObj.getFieldByteList().size()); assertEquals((Byte) (byte) 2, managedObj.getFieldByteList().get(0)); assertEquals((Byte) (byte) 3, managedObj.getFieldByteList().get(1)); assertEquals(2, managedObj.getFieldDoubleList().size()); assertEquals((Double) 2D, managedObj.getFieldDoubleList().get(0)); assertEquals((Double) 3D, managedObj.getFieldDoubleList().get(1)); assertEquals(2, managedObj.getFieldFloatList().size()); assertEquals((Float) 2F, managedObj.getFieldFloatList().get(0)); assertEquals((Float) 3F, managedObj.getFieldFloatList().get(1)); assertEquals(2, managedObj.getFieldDateList().size()); assertEquals(new Date(2L), managedObj.getFieldDateList().get(0)); assertEquals(new Date(3L), managedObj.getFieldDateList().get(1)); // Makes sure that excess object by default value is not created. assertEquals(2, realm.where(RandomPrimaryKey.class).count()); } @Test public void copyFromRealm_defaultValuesAreIgnored() { final DefaultValueOfField managedObj; realm.beginTransaction(); { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + "".modified""); obj.setFieldString(DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + "".modified""); obj.setFieldRandomString(""non-random""); obj.setFieldShort((short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1)); obj.setFieldInt(DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1); obj.setFieldLongPrimaryKey(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1); obj.setFieldLong(DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1); obj.setFieldByte((byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1)); obj.setFieldFloat(DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1); obj.setFieldDouble(DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1); obj.setFieldBoolean(!DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); obj.setFieldDate(new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1)); obj.setFieldBinary(new byte[] {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}); final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); fieldObjectValue.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); obj.setFieldObject(fieldObjectValue); final RealmList list = new RealmList(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2); list.add(listItem); obj.setFieldList(list); obj.setFieldStringList(new RealmList<>(""2"", ""3"")); obj.setFieldBooleanList(new RealmList<>(true, false)); obj.setFieldBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); obj.setFieldLongList(new RealmList<>(2L, 3L)); obj.setFieldIntegerList(new RealmList<>(2, 3)); obj.setFieldShortList(new RealmList<>((short) 2, (short) 3)); obj.setFieldByteList(new RealmList<>((byte) 2, (byte) 3)); obj.setFieldDoubleList(new RealmList<>(2D, 3D)); obj.setFieldFloatList(new RealmList<>(2F, 3F)); obj.setFieldDateList(new RealmList<>(new Date(2L), new Date(3L))); managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); final DefaultValueOfField copy = realm.copyFromRealm(managedObj); assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE, copy.getFieldIgnored()); assertEquals(managedObj.getFieldString(), copy.getFieldString()); assertEquals(managedObj.getFieldRandomString(), copy.getFieldRandomString()); assertEquals(managedObj.getFieldShort(), copy.getFieldShort()); assertEquals(managedObj.getFieldInt(), copy.getFieldInt()); assertEquals(managedObj.getFieldLongPrimaryKey(), copy.getFieldLongPrimaryKey()); assertEquals(managedObj.getFieldLong(), copy.getFieldLong()); assertEquals(managedObj.getFieldByte(), copy.getFieldByte()); assertEquals(managedObj.getFieldFloat(), copy.getFieldFloat(), 0F); assertEquals(managedObj.getFieldDouble(), copy.getFieldDouble(), 0D); assertEquals(managedObj.isFieldBoolean(), copy.isFieldBoolean()); assertEquals(managedObj.getFieldDate(), copy.getFieldDate()); assertArrayEquals(managedObj.getFieldBinary(), copy.getFieldBinary()); assertEquals(managedObj.getFieldObject().getFieldInt(), copy.getFieldObject().getFieldInt()); assertEquals(1, copy.getFieldList().size()); //noinspection ConstantConditions assertEquals(managedObj.getFieldList().first().getFieldInt(), copy.getFieldList().first().getFieldInt()); assertEquals(2, managedObj.getFieldStringList().size()); assertEquals(""2"", managedObj.getFieldStringList().get(0)); assertEquals(""3"", managedObj.getFieldStringList().get(1)); assertEquals(2, managedObj.getFieldBooleanList().size()); assertEquals(true, managedObj.getFieldBooleanList().get(0)); assertEquals(false, managedObj.getFieldBooleanList().get(1)); assertEquals(2, managedObj.getFieldBinaryList().size()); assertArrayEquals(new byte[] {2}, managedObj.getFieldBinaryList().get(0)); assertArrayEquals(new byte[] {3}, managedObj.getFieldBinaryList().get(1)); assertEquals(2, managedObj.getFieldLongList().size()); assertEquals((Long) 2L, managedObj.getFieldLongList().get(0)); assertEquals((Long) 3L, managedObj.getFieldLongList().get(1)); assertEquals(2, managedObj.getFieldIntegerList().size()); assertEquals((Integer) 2, managedObj.getFieldIntegerList().get(0)); assertEquals((Integer) 3, managedObj.getFieldIntegerList().get(1)); assertEquals(2, managedObj.getFieldShortList().size()); assertEquals((Short) (short) 2, managedObj.getFieldShortList().get(0)); assertEquals((Short) (short) 3, managedObj.getFieldShortList().get(1)); assertEquals(2, managedObj.getFieldByteList().size()); assertEquals((Byte) (byte) 2, managedObj.getFieldByteList().get(0)); assertEquals((Byte) (byte) 3, managedObj.getFieldByteList().get(1)); assertEquals(2, managedObj.getFieldDoubleList().size()); assertEquals((Double) 2D, managedObj.getFieldDoubleList().get(0)); assertEquals((Double) 3D, managedObj.getFieldDoubleList().get(1)); assertEquals(2, managedObj.getFieldFloatList().size()); assertEquals((Float) 2F, managedObj.getFieldFloatList().get(0)); assertEquals((Float) 3F, managedObj.getFieldFloatList().get(1)); assertEquals(2, managedObj.getFieldDateList().size()); assertEquals(new Date(2L), managedObj.getFieldDateList().get(0)); assertEquals(new Date(3L), managedObj.getFieldDateList().get(1)); } // Tests close Realm in another thread different from where it is created. @Test public void close_differentThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AssertionFailedError threadAssertionError[] = new AssertionFailedError[1]; final Thread thatThread = new Thread(new Runnable() { @Override public void run() { try { realm.close(); threadAssertionError[0] = new AssertionFailedError( ""Close realm in a different thread should throw IllegalStateException.""); } catch (IllegalStateException ignored) { } latch.countDown(); } }); thatThread.start(); // Timeout should never happen. TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } // After exception thrown in another thread, nothing should be changed to the realm in this thread. realm.checkIfValid(); realm.close(); realm = null; } @Test public void isClosed() { assertFalse(realm.isClosed()); realm.close(); assertTrue(realm.isClosed()); } // Tests Realm#isClosed() in another thread different from where it is created. @Test public void isClosed_differentThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AssertionFailedError threadAssertionError[] = new AssertionFailedError[1]; final Thread thatThread = new Thread(new Runnable() { @Override public void run() { try { realm.isClosed(); threadAssertionError[0] = new AssertionFailedError( ""Call isClosed() of Realm instance in a different thread should throw IllegalStateException.""); } catch (IllegalStateException ignored) { } latch.countDown(); } }); thatThread.start(); // Timeout should never happen. TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } // After exception thrown in another thread, nothing should be changed to the realm in this thread. realm.checkIfValid(); assertFalse(realm.isClosed()); realm.close(); } // Realm validation & initialization is done once, still ColumnIndices // should be populated for the subsequent Realm sharing the same configuration // even if we skip initialization & validation. @Test public void columnIndicesIsPopulatedWhenSkippingInitialization() throws Throwable { final RealmConfiguration realmConfiguration = configFactory.createConfiguration(""columnIndices""); final Exception threadError[] = new Exception[1]; final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch mainThreadRealmDone = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfiguration); // This will populate columnIndices. try { bgRealmOpened.countDown(); TestHelper.awaitOrFail(mainThreadRealmDone); realm.close(); bgRealmClosed.countDown(); } catch (Exception e) { threadError[0] = e; } finally { if (!realm.isClosed()) { realm.close(); } } } }).start(); TestHelper.awaitOrFail(bgRealmOpened); Realm realm = Realm.getInstance(realmConfiguration); realm.where(AllTypes.class).equalTo(""columnString"", ""Foo"").findAll(); // This would crash if columnIndices == null. realm.close(); mainThreadRealmDone.countDown(); TestHelper.awaitOrFail(bgRealmClosed); if (threadError[0] != null) { throw threadError[0]; } } @Test public void isInTransaction() { assertFalse(realm.isInTransaction()); realm.beginTransaction(); assertTrue(realm.isInTransaction()); realm.commitTransaction(); assertFalse(realm.isInTransaction()); realm.beginTransaction(); assertTrue(realm.isInTransaction()); realm.cancelTransaction(); assertFalse(realm.isInTransaction()); } // Test for https://github.com/realm/realm-java/issues/1646 @Test public void closingRealmWhileOtherThreadIsOpeningRealm() throws Exception { final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch endLatch = new CountDownLatch(1); final List exception = new ArrayList(); new Thread() { @Override public void run() { try { startLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); return; } final Realm realm = Realm.getInstance(realmConfig); try { realm.where(AllTypes.class).equalTo(""columnLong"", 0L).findFirst(); } catch (Exception e) { exception.add(e); } finally { endLatch.countDown(); realm.close(); } } }.start(); // Prevents for another thread to enter Realm.createAndValidate(). synchronized (BaseRealm.class) { startLatch.countDown(); // Waits for another thread's entering Realm.createAndValidate(). SystemClock.sleep(100L); realm.close(); realm = null; } TestHelper.awaitOrFail(endLatch); if (!exception.isEmpty()) { throw exception.get(0); } } // Bug reported https://github.com/realm/realm-java/issues/1728. // Root cause is validatedRealmFiles will be cleaned when any thread's Realm ref counter reach 0. @Test public void openRealmWhileTransactionInAnotherThread() throws Exception { final CountDownLatch realmOpenedInBgLatch = new CountDownLatch(1); final CountDownLatch realmClosedInFgLatch = new CountDownLatch(1); final CountDownLatch transBeganInBgLatch = new CountDownLatch(1); final CountDownLatch fgFinishedLatch = new CountDownLatch(1); final CountDownLatch bgFinishedLatch = new CountDownLatch(1); final List exception = new ArrayList(); // Step 1: testRealm is opened already. Thread thread = new Thread(new Runnable() { @Override public void run() { // Step 2: Opens realm in background thread. Realm realm = Realm.getInstance(realmConfig); realmOpenedInBgLatch.countDown(); try { realmClosedInFgLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); realm.close(); return; } // Step 4: Starts transaction in background. realm.beginTransaction(); transBeganInBgLatch.countDown(); try { fgFinishedLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); } // Step 6: Cancels Transaction and closes realm in background. realm.cancelTransaction(); realm.close(); bgFinishedLatch.countDown(); } }); thread.start(); TestHelper.awaitOrFail(realmOpenedInBgLatch); // Step 3: Closes all realm instances in foreground thread. realm.close(); realmClosedInFgLatch.countDown(); TestHelper.awaitOrFail(transBeganInBgLatch); // Step 5: Gets a new Realm instance in foreground. realm = Realm.getInstance(realmConfig); fgFinishedLatch.countDown(); TestHelper.awaitOrFail(bgFinishedLatch); if (!exception.isEmpty()) { throw exception.get(0); } } @Test public void isEmpty() { RealmConfiguration realmConfig = configFactory.createConfiguration(""empty_test.realm""); Realm emptyRealm = Realm.getInstance(realmConfig); assertTrue(emptyRealm.isEmpty()); emptyRealm.beginTransaction(); PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); emptyRealm.copyToRealm(obj); assertFalse(emptyRealm.isEmpty()); emptyRealm.cancelTransaction(); assertTrue(emptyRealm.isEmpty()); emptyRealm.beginTransaction(); obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); emptyRealm.copyToRealm(obj); emptyRealm.commitTransaction(); assertFalse(emptyRealm.isEmpty()); emptyRealm.close(); } @Test public void copyFromRealm_invalidObjectThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); obj.deleteFromRealm(); realm.commitTransaction(); try { realm.copyFromRealm(obj); fail(); } catch (IllegalArgumentException ignored) { } try { realm.copyFromRealm(new AllTypes()); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void copyFromRealm_invalidDepthThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(obj, -1); } @Test public void copyFromRealm() { populateTestRealm(); AllTypes realmObject = realm.where(AllTypes.class).sort(""columnLong"").findAll().first(); AllTypes unmanagedObject = realm.copyFromRealm(realmObject); assertArrayEquals(realmObject.getColumnBinary(), unmanagedObject.getColumnBinary()); assertEquals(realmObject.getColumnString(), unmanagedObject.getColumnString()); assertEquals(realmObject.getColumnLong(), unmanagedObject.getColumnLong()); assertEquals(realmObject.getColumnFloat(), unmanagedObject.getColumnFloat(), 0.00000000001); assertEquals(realmObject.getColumnDouble(), unmanagedObject.getColumnDouble(), 0.00000000001); assertEquals(realmObject.isColumnBoolean(), unmanagedObject.isColumnBoolean()); assertEquals(realmObject.getColumnDate(), unmanagedObject.getColumnDate()); assertEquals(realmObject.getColumnObjectId(), unmanagedObject.getColumnObjectId()); assertEquals(realmObject.getColumnDecimal128(), unmanagedObject.getColumnDecimal128()); assertEquals(realmObject.getColumnUUID(), unmanagedObject.getColumnUUID()); assertEquals(realmObject.getColumnRealmAny(), unmanagedObject.getColumnRealmAny()); } @Test public void copyFromRealm_newCopyEachTime() { populateTestRealm(); AllTypes realmObject = realm.where(AllTypes.class).sort(""columnLong"").findAll().first(); AllTypes unmanagedObject1 = realm.copyFromRealm(realmObject); AllTypes unmanagedObject2 = realm.copyFromRealm(realmObject); assertFalse(unmanagedObject1 == unmanagedObject2); assertNotSame(unmanagedObject1, unmanagedObject2); } // Tests that the object graph is copied as it is and no extra copies are made. // 1) (A -> B/[B,C]) // 2) (C -> B/[B,A]) // A copy should result in only 3 distinct objects. @Test public void copyFromRealm_cyclicObjectGraph() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); objA.setObject(objB); objC.setObject(objB); objA.getObjects().add(objB); objA.getObjects().add(objC); objC.getObjects().add(objB); objC.getObjects().add(objA); realm.commitTransaction(); CyclicType copyA = realm.copyFromRealm(objA); CyclicType copyB = copyA.getObject(); CyclicType copyC = copyA.getObjects().get(1); assertEquals(""A"", copyA.getName()); assertEquals(""B"", copyB.getName()); assertEquals(""C"", copyC.getName()); // Asserts object equality on the object graph. assertTrue(copyA.getObject() == copyC.getObject()); assertTrue(copyA.getObjects().get(0) == copyC.getObjects().get(0)); assertTrue(copyA == copyC.getObjects().get(1)); assertTrue(copyC == copyA.getObjects().get(1)); } // Tests that for (A -> B -> C) for maxDepth = 1, result is (A -> B -> null). @Test public void copyFromRealm_checkMaxDepth() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); objA.setObject(objB); objC.setObject(objC); objA.getObjects().add(objB); objA.getObjects().add(objC); realm.commitTransaction(); CyclicType copyA = realm.copyFromRealm(objA, 1); assertNull(copyA.getObject().getObject()); } // Tests that depth restriction is calculated from the top-most encountered object, i.e. it is possible for some // objects to exceed the depth limit. // A -> B -> C -> D -> E // A -> D -> E // D is both at depth 1 and 3. For maxDepth = 3, E should still be copied. @Test public void copyFromRealm_sameObjectDifferentDepths() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); CyclicType objD = realm.createObject(CyclicType.class); objD.setName(""D""); CyclicType objE = realm.createObject(CyclicType.class); objE.setName(""E""); objA.setObject(objB); objB.setObject(objC); objC.setObject(objD); objD.setObject(objE); objA.setOtherObject(objD); realm.commitTransaction(); // Object is filled before otherObject. (because of field order - WARNING: Not guaranteed) // This means that the object will be encountered first time at max depth, so E will not be copied. // If the object cache does not handle this, otherObject will be wrong. CyclicType copyA = realm.copyFromRealm(objA, 3); assertEquals(""E"", copyA.getOtherObject().getObject().getName()); } @Test public void copyFromRealm_list_invalidListThrows() { realm.beginTransaction(); AllTypes object = realm.createObject(AllTypes.class); List list = new RealmList(object); object.deleteFromRealm(); realm.commitTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(list); } @Test public void copyFromRealm_emptyList() { RealmResults results = realm.where(AllTypes.class).alwaysFalse().findAll(); List copy = realm.copyFromRealm(results); assertEquals(0, copy.size()); } @Test public void copyFromRealm_list_invalidDepthThrows() { RealmResults results = realm.where(AllTypes.class).findAll(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(results, -1); } // Tests that the same Realm objects in a list result in the same Java in-memory copy. // List: A -> [(B -> C), (B -> C)] should result in only 2 copied objects A and B and not A1, B1, A2, B2 @Test public void copyFromRealm_list_sameElements() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); objB.setObject(objC); objA.getObjects().add(objB); objA.getObjects().add(objB); realm.commitTransaction(); List results = realm.copyFromRealm(objA.getObjects()); assertEquals(2, results.size()); assertEquals(""B"", results.get(0).getName()); assertTrue(results.get(0) == results.get(1)); } @Test public void copyFromRealm_dynamicRealmObjectThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { realm.copyFromRealm(dObj); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void copyFromRealm_dynamicRealmListThrows() { DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); dynamicRealm.beginTransaction(); RealmList dynamicList = dynamicRealm.createObject(AllTypes.CLASS_NAME).getList(AllTypes.FIELD_REALMLIST); DynamicRealmObject dObj = dynamicRealm.createObject(Dog.CLASS_NAME); dynamicList.add(dObj); dynamicRealm.commitTransaction(); try { realm.copyFromRealm(dynamicList); fail(); } catch (IllegalArgumentException ignored) { } finally { dynamicRealm.close(); } } // Tests if close can be called from Realm change listener when there is no other listeners. @Test public void closeRealmInChangeListener() { looperThread.runBlocking(() -> { Realm realm = Realm.getInstance(realmConfig); looperThread.closeAfterTest(realm); final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } // Tests if close can be called from Realm change listener when there is a listener on empty Realm Object. @Test public void closeRealmInChangeListenerWhenThereIsListenerOnEmptyObject() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { } }; // Change listener on Realm final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); // Change listener on Empty Object final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); allTypes.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } // Tests if close can be called from Realm change listener when there is an listener on non-empty Realm Object. @Test public void closeRealmInChangeListenerWhenThereIsListenerOnObject() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { } }; final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 2) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); // Change listener on Realm Object. final AllTypes allTypes = realm.where(AllTypes.class).findFirst(); allTypes.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } // Tests if close can be called from Realm change listener when there is an listener on RealmResults. @Test public void closeRealmInChangeListenerWhenThereIsListenerOnResults() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); final RealmChangeListener> dummyListener = new RealmChangeListener>() { @Override public void onChange(RealmResults object) { } }; final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); // Change listener on Realm results. RealmResults results = realm.where(AllTypes.class).findAll(); results.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } @Test public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); try { realm.addChangeListener(null); fail(""adding null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { looperThread.testComplete(); } }); } @Test public void addChangeListener_throwOnAddingNullListenerFromNonLooperThread() throws Throwable { TestHelper.executeOnNonLooperThread(new TestHelper.Task() { @Override public void run() throws Exception { final Realm realm = Realm.getInstance(realmConfig); //noinspection TryFinallyCanBeTryWithResources try { realm.addChangeListener(null); fail(""adding null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { realm.close(); } } }); } @Test public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); try { realm.removeChangeListener(null); fail(""removing null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { looperThread.testComplete(); } }); } @Test public void removeChangeListener_throwOnRemovingNullListenerFromNonLooperThread() throws Throwable { TestHelper.executeOnNonLooperThread(new TestHelper.Task() { @Override public void run() throws Exception { final Realm realm = Realm.getInstance(realmConfig); //noinspection TryFinallyCanBeTryWithResources try { realm.removeChangeListener(null); fail(""removing null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { realm.close(); } } }); } @Test public void removeChangeListenerThrowExceptionOnWrongThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); Realm realm = Realm.getInstance(realmConfig); Thread thread = new Thread(() -> { try { realm.removeChangeListener(object -> {}); fail(""Should not be able to invoke removeChangeListener""); } catch (IllegalStateException ignored) { } finally { signalTestFinished.countDown(); } }); thread.start(); try { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); realm.close(); } } @Test public void removeAllChangeListenersThrowExceptionOnWrongThreadThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); Realm realm = Realm.getInstance(realmConfig); Thread thread = new Thread(() -> { try { realm.removeAllChangeListeners(); fail(""Should not be able to invoke removeChangeListener""); } catch (IllegalStateException ignored) { } finally { signalTestFinished.countDown(); } }); thread.start(); try { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); realm.close(); } } @Test public void deleteAll() { realm.beginTransaction(); realm.createObject(AllTypes.class); realm.createObject(Owner.class).setCat(realm.createObject(Cat.class)); realm.commitTransaction(); assertEquals(1, realm.where(AllTypes.class).count()); assertEquals(1, realm.where(Owner.class).count()); assertEquals(1, realm.where(Cat.class).count()); realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(AllTypes.class).count()); assertEquals(0, realm.where(Owner.class).count()); assertEquals(0, realm.where(Cat.class).count()); assertTrue(realm.isEmpty()); } // Test for https://github.com/realm/realm-java/issues/5745 @Test public void deleteAll_realmWithMoreTables() { realm.close(); RealmConfiguration config1 = configFactory.createConfigurationBuilder() .name(""deleteAllTest.realm"") .schema(StringOnly.class, StringAndInt.class) .build(); realm = Realm.getInstance(config1); realm.executeTransaction(r -> { r.createObject(StringOnly.class); r.createObject(StringAndInt.class); }); realm.close(); RealmConfiguration config2 = configFactory.createConfigurationBuilder() .name(""deleteAllTest.realm"") .schema(StringOnly.class) .build(); realm = Realm.getInstance(config2); realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertTrue(realm.isEmpty()); realm.close(); // deleteAll() will only delete tables part of the schema, so reopening with the old // should reveal the old data realm = Realm.getInstance(config1); assertFalse(realm.isEmpty()); assertEquals(1, realm.where(StringAndInt.class).count()); } @Test public void waitForChange_emptyDataChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); // Waits in background. final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); realm.beginTransaction(); realm.commitTransaction(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); assertEquals(0, bgRealmWaitForChangeResult.get()); } @Test public void waitForChange_withDataChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); // Waits in background. final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); populateTestRealm(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); assertEquals(TEST_DATA_SIZE, bgRealmWaitForChangeResult.get()); } @Test public void waitForChange_syncBackgroundRealmResults() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmResultSize = new AtomicLong(0); // Wait in background final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); RealmResults results = realm.where(AllTypes.class).findAll(); // First makes sure the results is empty. bgRealmResultSize.set(results.size()); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmResultSize.set(results.size()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); // Background result should be empty. assertEquals(0, bgRealmResultSize.get()); populateTestRealm(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); // Once RealmResults are synchronized after waitForChange, the result size should be what we expect. assertEquals(TEST_DATA_SIZE, bgRealmResultSize.get()); } @Test public void stopWaitForChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(true); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }).start(); TestHelper.awaitOrFail(bgRealmOpened); Thread.sleep(200); bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmChangeResult.get()); } // Tests if waitForChange doesn't blocks once stopWaitForChange has been called before. @Test public void waitForChange_stopWaitForChangeDisablesWaiting() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmStopped = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmFirstWaitResult = new AtomicBoolean(true); final AtomicBoolean bgRealmSecondWaitResult = new AtomicBoolean(false); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmFirstWaitResult.set(realm.waitForChange()); bgRealmStopped.countDown(); bgRealmSecondWaitResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }).start(); TestHelper.awaitOrFail(bgRealmOpened); bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmStopped); assertFalse(bgRealmFirstWaitResult.get()); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmSecondWaitResult.get()); } @Test public void waitForChange_stopWaitForChangeReleasesAllWaitingThreads() throws InterruptedException { final CountDownLatch bgRealmsOpened = new CountDownLatch(2); final CountDownLatch bgRealmsClosed = new CountDownLatch(2); final AtomicBoolean bgRealmFirstWaitResult = new AtomicBoolean(true); final AtomicBoolean bgRealmSecondWaitResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. Thread thread1 = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmsOpened.countDown(); bgRealmFirstWaitResult.set(realm.waitForChange()); realm.close(); bgRealmsClosed.countDown(); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmsOpened.countDown(); bgRealmSecondWaitResult.set(realm.waitForChange());//In Core 6 calling stopWaitForChange will release all waiting threads // which causes query below to run before `populateTestRealm` happens bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmsClosed.countDown(); } }); thread1.start(); thread2.start(); TestHelper.awaitOrFail(bgRealmsOpened); bgRealm.get().stopWaitForChange(); // Waits for Thread 2 to wait. Thread.sleep(500); populateTestRealm(); TestHelper.awaitOrFail(bgRealmsClosed); assertFalse(bgRealmFirstWaitResult.get()); assertFalse(bgRealmSecondWaitResult.get()); assertEquals(0, bgRealmWaitForChangeResult.get()); } // Checks if waitForChange() does not respond to Thread.interrupt(). @Test public void waitForChange_interruptingThread() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicReference bgRealmWaitResult = new AtomicReference(); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmWaitResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); // Makes sure background thread goes to wait. Thread.sleep(500); // Interrupting a thread should neither cause any side effect nor terminate the Background Realm from waiting. thread.interrupt(); assertTrue(thread.isInterrupted()); assertEquals(null, bgRealmWaitResult.get()); // Now we'll stop realm from waiting. bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmWaitResult.get()); } @Test public void waitForChange_onLooperThread() throws Throwable { final CountDownLatch bgRealmClosed = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Realm realm = Realm.getInstance(realmConfig); try { realm.waitForChange(); fail(); } catch (IllegalStateException ignored) { } finally { realm.close(); bgRealmClosed.countDown(); } } }); thread.start(); TestHelper.awaitOrFail(bgRealmClosed); } // Cannot wait inside of a transaction. @Test(expected = IllegalStateException.class) public void waitForChange_illegalWaitInsideTransaction() { realm.beginTransaction(); realm.waitForChange(); } @Test public void waitForChange_stopWaitingOnClosedRealmThrows() throws InterruptedException { final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicReference bgRealm = new AtomicReference(); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmClosed); try { bgRealm.get().stopWaitForChange(); fail(""Cannot stop a closed Realm from waiting""); } catch (IllegalStateException expected) { } } // waitForChange & stopWaitForChange within a simple Thread wrapper. @Test public void waitForChange_runWithRealmThread() throws InterruptedException { final CountDownLatch bgRealmStarted = new CountDownLatch(1); final CountDownLatch bgRealmFished = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmResultSize = new AtomicLong(0); RealmThread thread = new RealmThread(realmConfig, new RealmThread.RealmRunnable() { @Override public void run(Realm realm) { bgRealmStarted.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmResultSize.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmFished.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmStarted); populateTestRealm(); TestHelper.awaitOrFail(bgRealmFished); assertTrue(bgRealmChangeResult.get()); assertEquals(TEST_DATA_SIZE, bgRealmResultSize.get()); } @Test public void waitForChange_endRealmThread() throws InterruptedException { final CountDownLatch bgRealmStarted = new CountDownLatch(1); final CountDownLatch bgRealmFished = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(true); RealmThread thread = new RealmThread(realmConfig, new RealmThread.RealmRunnable() { @Override public void run(Realm realm) { bgRealmStarted.countDown(); bgRealmChangeResult.set(realm.waitForChange()); realm.close(); bgRealmFished.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmStarted); thread.end(); TestHelper.awaitOrFail(bgRealmFished); assertFalse(bgRealmChangeResult.get()); } @Test public void getGlobalInstanceCount() { final CountDownLatch bgDone = new CountDownLatch(1); final RealmConfiguration config = configFactory.createConfiguration(""globalCountTest""); assertEquals(0, Realm.getGlobalInstanceCount(config)); // Opens thread local Realm. Realm realm = Realm.getInstance(config); assertEquals(1, Realm.getGlobalInstanceCount(config)); Realm realm1 = Realm.getInstance(config); assertEquals(1, Realm.getGlobalInstanceCount(config)); // Even though each Realm type points to the same Realm on disk, we report them as // multiple global instances // Opens thread local DynamicRealm. DynamicRealm dynRealm = DynamicRealm.getInstance(config); assertEquals(2, Realm.getGlobalInstanceCount(config)); // Create frozen Realms. Realm frozenRealm = realm.freeze(); assertTrue(frozenRealm.isFrozen()); assertEquals(3, Realm.getGlobalInstanceCount(config)); DynamicRealm frozenDynamicRealm = dynRealm.freeze(); assertTrue(frozenDynamicRealm.isFrozen()); assertEquals(4, Realm.getGlobalInstanceCount(config)); // Opens Realm in another thread. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(config); assertEquals(5, Realm.getGlobalInstanceCount(config)); realm.close(); assertEquals(4, Realm.getGlobalInstanceCount(config)); bgDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgDone); dynRealm.close(); assertEquals(3, Realm.getGlobalInstanceCount(config)); realm.close(); realm1.close(); // Fully closing the live Realm also closes all frozen Realms assertEquals(0, Realm.getGlobalInstanceCount(config)); assertTrue(frozenRealm.isClosed()); assertTrue(frozenDynamicRealm.isClosed()); } @Test public void getLocalInstanceCount() { final RealmConfiguration config = configFactory.createConfiguration(""localInstanceCount""); assertEquals(0, Realm.getLocalInstanceCount(config)); // Opens thread local Realm. Realm realm = Realm.getInstance(config); assertEquals(1, Realm.getLocalInstanceCount(config)); // Opens thread local DynamicRealm. DynamicRealm dynRealm = DynamicRealm.getInstance(config); assertEquals(2, Realm.getLocalInstanceCount(config)); dynRealm.close(); assertEquals(1, Realm.getLocalInstanceCount(config)); realm.close(); assertEquals(0, Realm.getLocalInstanceCount(config)); } @Test public void namedPipeDirForExternalStorage() { // Test for https://github.com/realm/realm-java/issues/3140 realm.close(); realm = null; final File namedPipeDir = OsSharedRealm.getTemporaryDirectory(); assertTrue(namedPipeDir.isDirectory()); TestHelper.deleteRecursively(namedPipeDir); //noinspection ResultOfMethodCallIgnored namedPipeDir.mkdirs(); final File externalFilesDir = context.getExternalFilesDir(null); final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(externalFilesDir) .name(""external.realm"") .build(); Realm.deleteRealm(config); // Test if it works when the namedPipeDir is empty. Realm realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); assertTrue(namedPipeDir.isDirectory()); Assume.assumeTrue(""SELinux is not enforced on this device."", TestHelper.isSelinuxEnforcing()); // Only checks the fifo file created by call, since all Realm instances share the same fifo created by // external_commit_helper which might not be created in the newly created dir if there are Realm instances // are not deleted when TestHelper.deleteRecursively(namedPipeDir) called. File[] files = namedPipeDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.matches(""realm_.*cv""); } }); assertEquals(2, files.length); // Tests if it works when the namedPipeDir and the named pipe files already exist. realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); } @Test(expected = IllegalStateException.class) public void getInstanceAsync_nonLooperThreadShouldThrow() { Realm.getInstanceAsync(realmConfig, new Realm.Callback() { @Override public void onSuccess(Realm realm) { fail(); } }); } @Test public void getInstanceAsync_nullConfigShouldThrow() { looperThread.runBlocking(() -> { try { Realm.getInstanceAsync(null, new Realm.Callback() { @Override public void onSuccess(Realm realm) { fail(); } }); fail(); } catch(IllegalArgumentException ignore) { } looperThread.testComplete(); }); } // Verify that the logic for waiting for the users file dir to be come available isn't totally broken // This is pretty hard to test, so forced to break encapsulation in this case. @Test public void init_waitForFilesDir() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { java.lang.reflect.Method m = Realm.class.getDeclaredMethod(""checkFilesDirAvailable"", Context.class); m.setAccessible(true); // A) Check it fails if getFilesDir is never created Context mockContext = mock(Context.class); when(mockContext.getFilesDir()).thenReturn(null); try { m.invoke(null, mockContext); fail(); } catch (InvocationTargetException e) { assertEquals(IllegalStateException.class, e.getCause().getClass()); } // B) Check we return if the filesDir becomes available after a while mockContext = mock(Context.class); when(mockContext.getFilesDir()).then(new Answer() { int calls = 0; File userFolder = tmpFolder.newFolder(); @Override public File answer(InvocationOnMock invocationOnMock) throws Throwable { calls++; return (calls > 5) ? userFolder : null; // Start returning the correct folder after 5 attempts } }); assertNull(m.invoke(null, mockContext)); } @Test public void refresh_triggerNotifications() { looperThread.runBlocking(() -> { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(0, results.size()); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { assertEquals(1, results.size()); listenerCalled.set(true); } }); // Advance the Realm on a background while blocking this thread. When we refresh, it should trigger // the listener. new Thread(() -> { Realm realm1 = Realm.getInstance(realmConfig); realm1.beginTransaction(); realm1.createObject(AllTypes.class); realm1.commitTransaction(); realm1.close(); bgThreadDone.countDown(); }).start(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertTrue(listenerCalled.get()); looperThread.testComplete(); }); } @Test public void refresh_nonLooperThreadAdvances() { final CountDownLatch bgThreadDone = new CountDownLatch(1); RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(0, results.size()); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(RealmTests.this.realm.getConfiguration()); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); realm.close(); bgThreadDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertEquals(1, results.size()); } @Test public void refresh_forceSynchronousNotifications() { looperThread.runBlocking(() -> { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); RealmResults results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { // Will be forced synchronous assertEquals(1, results.size()); listenerCalled.set(true); } }); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); realm.close(); bgThreadDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertTrue(listenerCalled.get()); looperThread.testComplete(); }); } @Test public void refresh_insideTransactionThrows() { realm.beginTransaction(); try { realm.refresh(); fail(); } catch (IllegalStateException ignored) { } realm.cancelTransaction(); } @Test public void beginTransaction_readOnlyThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""readonly.realm"") .schema(StringOnlyReadOnly.class) .assetFile(""readonly.realm"") .readOnly() .build(); Realm realm = Realm.getInstance(config); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage(), CoreMatchers.containsString(""Can't perform transactions on read-only Realms."")); } finally { realm.close(); } } @Test public void getInstance_wrongSchemaInReadonlyThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""readonly.realm"") .schema(StringOnlyReadOnly.class, AllJavaTypes.class) .assetFile(""readonly.realm"") .readOnly() .build(); // This will throw because the Realm doesn't have the correct schema, and a new file cannot be re-created // because it is read only. try { realm = Realm.getInstance(config); fail(); } catch (RealmMigrationNeededException ignored) { } } // https://github.com/realm/realm-java/issues/5570 @Test public void getInstance_migrationExceptionThrows_migrationBlockDefiend_realmInstancesShouldBeClosed() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""readonly.realm"") .schema(StringOnlyReadOnly.class, AllJavaTypes.class) .schemaVersion(2) .assetFile(""readonly.realm"") .migration(new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } }) .build(); try { realm = Realm.getInstance(config); fail(); } catch (RealmMigrationNeededException ignored) { // No Realm instance should be opened at this time. Realm.deleteRealm(config); } } @Test public void hittingMaxNumberOfVersionsThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""versions-test.realm"") .maxNumberOfActiveVersions(1) .build(); Realm realm = Realm.getInstance(config); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertTrue(e.getMessage().contains(""Number of active versions (2) in the Realm exceeded the limit of 1"")); } finally { realm.close(); } } // Test for https://github.com/realm/realm-java/issues/6977 @Test public void numberOfVersionsDecreasedOnClose() { realm.close(); looperThread.runBlocking(() -> { int count = 50; final CountDownLatch bgThreadDoneLatch = new CountDownLatch(count); RealmConfiguration config = configFactory.createConfigurationBuilder() // The multiple embedded threads seems to cause trouble with factory's directory setting .directory(context.getFilesDir()) .name(""versions-test.realm"") .maxNumberOfActiveVersions(5) .build(); Realm.deleteRealm(config); // Synchronizes between change listener and Background writes so they operate in lockstep. AtomicReference guard = new AtomicReference<>(new CountDownLatch(1)); Realm realm = Realm.getInstance(config); looperThread.closeAfterTest(realm); realm.addChangeListener(callbackRealm -> { // This test catches a bug that caused ObjectStore to pin Realm versions // if a TableView was created inside a change notification and no elements // in the TableView was accessed. RealmResults query = realm.where(AllJavaTypes.class).findAll(); guard.get().countDown(); bgThreadDoneLatch.countDown(); if (bgThreadDoneLatch.getCount() == 0) { looperThread.testComplete(); } }); // Write a number of transactions in the background in a serial manner // in order to create a number of different versions. Done in serial // to allow the LooperThread to catch up. new Thread(() -> { for (int i = 0; i < count; i++) { Thread t = new Thread() { @Override public void run() { Realm realm = Realm.getInstance(config); realm.executeTransaction(bgRealm -> { }); realm.close(); } }; t.start(); try { t.join(); TestHelper.awaitOrFail(guard.get()); guard.set(new CountDownLatch(1)); } catch (InterruptedException e) { throw new RuntimeException(e); } } }).start(); }); } // Test for https://github.com/realm/realm-java/issues/6152 @Test @Ignore(""See https://github.com/realm/realm-java/issues/7628"") public void encryption_stressTest() { realm.close(); looperThread.runBlocking(() -> { final int WRITER_TRANSACTIONS = 50; final int TEST_OBJECTS = 100_000; final int MAX_STRING_LENGTH = 1000; final AtomicInteger id = new AtomicInteger(0); long seed = System.nanoTime(); Random random = new Random(seed); RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""encryption-stress-test.realm"") .encryptionKey(TestHelper.getRandomKey(seed)) .build(); Realm.deleteRealm(config); Thread t = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(config); for (int i = 0; i < WRITER_TRANSACTIONS; i++) { realm.executeTransaction(r -> { for (int j = 0; j < (TEST_OBJECTS / WRITER_TRANSACTIONS); j++) { AllJavaTypes obj = new AllJavaTypes(id.incrementAndGet()); obj.setFieldString(TestHelper.getRandomString(random.nextInt(MAX_STRING_LENGTH))); r.insert(obj); } }); } realm.close(); } }); t.start(); Realm realm = Realm.getInstance(config); looperThread.closeAfterTest(realm); RealmResults results = realm.where(AllJavaTypes.class).findAllAsync(); looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) { for (AllJavaTypes obj : results) { String s = obj.getFieldString(); } if (results.size() == TEST_OBJECTS) { try { t.join(5000); } catch (InterruptedException e) { fail(""workerthread failed to finish in time.""); } looperThread.testComplete(); } } }); }); } @Test public void getNumberOfActiveVersions() throws InterruptedException { CountDownLatch bgWritesCompleted = new CountDownLatch(1); CountDownLatch closeBgRealm = new CountDownLatch(1); assertEquals(2, realm.getNumberOfActiveVersions()); Thread t = new Thread(() -> { Realm bgRealm = Realm.getInstance(realmConfig); assertEquals(2, bgRealm.getNumberOfActiveVersions()); for (int i = 0; i < 5; i++) { bgRealm.executeTransaction(r -> { /* empty */ }); } assertEquals(6, bgRealm.getNumberOfActiveVersions()); bgWritesCompleted.countDown(); TestHelper.awaitOrFail(closeBgRealm); bgRealm.close(); }); t.start(); TestHelper.awaitOrFail(bgWritesCompleted); assertEquals(6, realm.getNumberOfActiveVersions()); closeBgRealm.countDown(); t.join(); realm.refresh(); // Release old versions for GC realm.beginTransaction(); realm.commitTransaction(); // Actually release the versions assertEquals(2, realm.getNumberOfActiveVersions()); realm.close(); try { realm.getNumberOfActiveVersions(); fail(); } catch (IllegalStateException ignore) { } } @Test public void getCachedInstanceDoNotTriggerStrictMode() { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyDeath() .build()); try { Realm.getInstance(realmConfig).close(); } finally { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .permitAll() .build()); } } @Test public void getCachedInstanceFromOtherThreadDoNotTriggerStrictMode() throws InterruptedException { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyDeath() .build()); try { Thread t = new Thread(() -> Realm.getInstance(realmConfig).close()); t.start(); t.join(); } finally { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .permitAll() .build()); } } } ","resultList " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.tests.lwjgl; import java.awt.BorderLayout; import java.awt.HeadlessException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.DefaultListSelectionModel; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; import javax.swing.UIManager; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl.LwjglFiles; import com.badlogic.gdx.backends.lwjgl.LwjglPreferences; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.tests.utils.CommandLineOptions; import com.badlogic.gdx.tests.utils.GdxTest; import com.badlogic.gdx.tests.utils.GdxTestWrapper; import com.badlogic.gdx.tests.utils.GdxTests; import com.badlogic.gdx.utils.SharedLibraryLoader; public class LwjglTestStarter extends JFrame { static CommandLineOptions options; public LwjglTestStarter () throws HeadlessException { super(""libGDX Tests""); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setContentPane(new TestList()); pack(); setSize(getWidth(), 600); setLocationRelativeTo(null); setVisible(true); } /** Runs the {@link GdxTest} with the given name. * * @param testName the name of a test class * @return {@code true} if the test was found and run, {@code false} otherwise */ public static boolean runTest (String testName) { boolean useGL30 = options.gl30; GdxTest test = GdxTests.newTest(testName); if (test == null) { return false; } LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.width = 640; config.height = 480; config.title = testName; config.forceExit = false; if (useGL30) { config.useGL30 = true; if (!SharedLibraryLoader.isMac) { config.gles30ContextMajorVersion = 4; config.gles30ContextMinorVersion = 3; } ShaderProgram.prependVertexCode = ""#version 140\n#define varying out\n#define attribute in\n""; ShaderProgram.prependFragmentCode = ""#version 140\n#define varying in\n#define texture2D texture\n#define gl_FragColor fragColor\nout vec4 fragColor;\n""; } else { config.useGL30 = false; ShaderProgram.prependVertexCode = """"; ShaderProgram.prependFragmentCode = """"; } new LwjglApplication(new GdxTestWrapper(test, options.logGLErrors), config); return true; } class TestList extends JPanel { public TestList () { setLayout(new BorderLayout()); final JButton [MASK] = new JButton(""Run Test""); final JList list = new JList(options.getCompatibleTests()); JScrollPane pane = new JScrollPane(list); DefaultListSelectionModel m = new DefaultListSelectionModel(); m.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); m.setLeadAnchorNotificationEnabled(false); list.setSelectionModel(m); list.addMouseListener(new MouseAdapter() { public void mouseClicked (MouseEvent event) { if (event.getClickCount() == 2) [MASK] .doClick(); } }); list.addKeyListener(new KeyAdapter() { public void keyPressed (KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) [MASK] .doClick(); } }); final Preferences prefs = new LwjglPreferences( new FileHandle(new LwjglFiles().getExternalStoragePath() + "".prefs/lwjgl-tests"")); list.setSelectedValue(prefs.getString(""last"", null), true); [MASK] .addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { String testName = (String)list.getSelectedValue(); prefs.putString(""last"", testName); prefs.flush(); dispose(); runTest(testName); } }); add(pane, BorderLayout.CENTER); add( [MASK] , BorderLayout.SOUTH); // GdxTest test = GdxTests.newTest(""BitmapFontFlipTest""); // new LwjglApplication(test, ""Test"", 480, 320, test.needsGL20()); } } /** Runs a libGDX test. * * If no arguments are provided on the command line, shows a list of tests to choose from. If an argument is present, the test * with that name will immediately be run. Additional options can be passed, see {@link CommandLineOptions} * * @param argv command line arguments */ public static void main (String[] argv) throws Exception { options = new CommandLineOptions(argv); if (options.startupTestName != null) { if (runTest(options.startupTestName)) { return; // Otherwise, fall back to showing the list } } UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); new LwjglTestStarter(); } } ","button " "/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.channel.nio; import io.netty.channel.AbstractEventLoopTest; import io.netty.channel.Channel; import io.netty.channel.DefaultSelectStrategyFactory; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopTaskQueueFactory; import io.netty.channel.SelectStrategy; import io.netty.channel.SelectStrategyFactory; import io.netty.channel.SingleThreadEventLoop; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.IntSupplier; import io.netty.util.concurrent.DefaultEventExecutorChooserFactory; import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.RejectedExecutionHandlers; import io.netty.util.concurrent.ThreadPerTaskExecutor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; public class NioEventLoopTest extends AbstractEventLoopTest { @Override protected EventLoopGroup newEventLoopGroup() { return new NioEventLoopGroup(); } @Override protected Class newChannel() { return NioServerSocketChannel.class; } @Test public void testRebuildSelector() { EventLoopGroup [MASK] = new NioEventLoopGroup(1); final NioEventLoop loop = (NioEventLoop) [MASK] .next(); try { Channel channel = new NioServerSocketChannel(); loop.register(channel).syncUninterruptibly(); Selector selector = loop.unwrappedSelector(); assertSame(selector, ((NioEventLoop) channel.eventLoop()).unwrappedSelector()); assertTrue(selector.isOpen()); // Submit to the EventLoop so we are sure its really executed in a non-async manner. loop.submit(new Runnable() { @Override public void run() { loop.rebuildSelector(); } }).syncUninterruptibly(); Selector newSelector = ((NioEventLoop) channel.eventLoop()).unwrappedSelector(); assertTrue(newSelector.isOpen()); assertNotSame(selector, newSelector); assertFalse(selector.isOpen()); channel.close().syncUninterruptibly(); } finally { [MASK] .shutdownGracefully(); } } @Test public void testScheduleBigDelayNotOverflow() { EventLoopGroup [MASK] = new NioEventLoopGroup(1); final EventLoop el = [MASK] .next(); Future future = el.schedule(new Runnable() { @Override public void run() { // NOOP } }, Long.MAX_VALUE, TimeUnit.MILLISECONDS); assertFalse(future.awaitUninterruptibly(1000)); assertTrue(future.cancel(true)); [MASK] .shutdownGracefully(); } @Test public void testInterruptEventLoopThread() throws Exception { EventLoopGroup [MASK] = new NioEventLoopGroup(1); final NioEventLoop loop = (NioEventLoop) [MASK] .next(); try { Selector selector = loop.unwrappedSelector(); assertTrue(selector.isOpen()); loop.submit(new Runnable() { @Override public void run() { // Interrupt the thread which should not end-up in a busy spin and // so the selector should not have been rebuild. Thread.currentThread().interrupt(); } }).syncUninterruptibly(); assertTrue(selector.isOpen()); final CountDownLatch latch = new CountDownLatch(2); loop.submit(new Runnable() { @Override public void run() { latch.countDown(); } }).syncUninterruptibly(); loop.schedule(new Runnable() { @Override public void run() { latch.countDown(); } }, 2, TimeUnit.SECONDS).syncUninterruptibly(); latch.await(); assertSame(selector, loop.unwrappedSelector()); assertTrue(selector.isOpen()); } finally { [MASK] .shutdownGracefully(); } } @Test @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void testSelectableChannel() throws Exception { NioEventLoopGroup [MASK] = new NioEventLoopGroup(1); NioEventLoop loop = (NioEventLoop) [MASK] .next(); try { Channel channel = new NioServerSocketChannel(); loop.register(channel).syncUninterruptibly(); channel.bind(new InetSocketAddress(0)).syncUninterruptibly(); SocketChannel selectableChannel = SocketChannel.open(); selectableChannel.configureBlocking(false); selectableChannel.connect(channel.localAddress()); final CountDownLatch latch = new CountDownLatch(1); loop.register(selectableChannel, SelectionKey.OP_CONNECT, new NioTask() { @Override public void channelReady(SocketChannel ch, SelectionKey key) { latch.countDown(); } @Override public void channelUnregistered(SocketChannel ch, Throwable cause) { } }); latch.await(); selectableChannel.close(); channel.close().syncUninterruptibly(); } finally { [MASK] .shutdownGracefully(); } } @SuppressWarnings(""deprecation"") @Test public void testTaskRemovalOnShutdownThrowsNoUnsupportedOperationException() throws Exception { final AtomicReference error = new AtomicReference(); final Runnable task = new Runnable() { @Override public void run() { // NOOP } }; // Just run often enough to trigger it normally. for (int i = 0; i < 1000; i++) { NioEventLoopGroup [MASK] = new NioEventLoopGroup(1); final NioEventLoop loop = (NioEventLoop) [MASK] .next(); Thread t = new Thread(new Runnable() { @Override public void run() { try { for (;;) { loop.execute(task); } } catch (Throwable cause) { error.set(cause); } } }); t.start(); [MASK] .shutdownNow(); t.join(); [MASK] .terminationFuture().syncUninterruptibly(); assertThat(error.get(), instanceOf(RejectedExecutionException.class)); error.set(null); } } @Test public void testRebuildSelectorOnIOException() { SelectStrategyFactory selectStrategyFactory = new SelectStrategyFactory() { @Override public SelectStrategy newSelectStrategy() { return new SelectStrategy() { private boolean thrown; @Override public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception { if (!thrown) { thrown = true; throw new IOException(); } return -1; } }; } }; EventLoopGroup [MASK] = new NioEventLoopGroup(1, new DefaultThreadFactory(""ioPool""), SelectorProvider.provider(), selectStrategyFactory); final NioEventLoop loop = (NioEventLoop) [MASK] .next(); try { Channel channel = new NioServerSocketChannel(); Selector selector = loop.unwrappedSelector(); loop.register(channel).syncUninterruptibly(); Selector newSelector = ((NioEventLoop) channel.eventLoop()).unwrappedSelector(); assertTrue(newSelector.isOpen()); assertNotSame(selector, newSelector); assertFalse(selector.isOpen()); channel.close().syncUninterruptibly(); } finally { [MASK] .shutdownGracefully(); } } @Test @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void testChannelsRegistered() throws Exception { NioEventLoopGroup [MASK] = new NioEventLoopGroup(1); final NioEventLoop loop = (NioEventLoop) [MASK] .next(); try { final Channel ch1 = new NioServerSocketChannel(); final Channel ch2 = new NioServerSocketChannel(); assertEquals(0, registeredChannels(loop)); assertTrue(loop.register(ch1).syncUninterruptibly().isSuccess()); assertTrue(loop.register(ch2).syncUninterruptibly().isSuccess()); assertEquals(2, registeredChannels(loop)); assertTrue(ch1.deregister().syncUninterruptibly().isSuccess()); int registered; // As SelectionKeys are removed in a lazy fashion in the JDK implementation we may need to query a few // times before we see the right number of registered chanels. while ((registered = registeredChannels(loop)) == 2) { Thread.sleep(50); } assertEquals(1, registered); } finally { [MASK] .shutdownGracefully(); } } // Only reliable if run from event loop private static int registeredChannels(final SingleThreadEventLoop loop) throws Exception { return loop.submit(new Callable() { @Override public Integer call() { return loop.registeredChannels(); } }).get(1, TimeUnit.SECONDS); } @Test public void testCustomQueue() { final AtomicBoolean called = new AtomicBoolean(); NioEventLoopGroup [MASK] = new NioEventLoopGroup(1, new ThreadPerTaskExecutor(new DefaultThreadFactory(NioEventLoopGroup.class)), DefaultEventExecutorChooserFactory.INSTANCE, SelectorProvider.provider(), DefaultSelectStrategyFactory.INSTANCE, RejectedExecutionHandlers.reject(), new EventLoopTaskQueueFactory() { @Override public Queue newTaskQueue(int maxCapacity) { called.set(true); return new LinkedBlockingQueue(maxCapacity); } }); final NioEventLoop loop = (NioEventLoop) [MASK] .next(); try { loop.submit(new Runnable() { @Override public void run() { // NOOP. } }).syncUninterruptibly(); assertTrue(called.get()); } finally { [MASK] .shutdownGracefully(); } } } ","group " "/* * Copyright 2016 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.channel.kqueue; import io.netty.channel.DefaultFileRegion; import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.unix.IovArray; import io.netty.channel.unix.PeerCredentials; import io.netty.channel.unix.Socket; import java.io.IOException; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import static io.netty.channel.kqueue.AcceptFilter.PLATFORM_UNSUPPORTED; import static io.netty.channel.kqueue.Native.CONNECT_TCP_FASTOPEN; import static io.netty.channel.unix.Errors.ERRNO_EINPROGRESS_NEGATIVE; import static io.netty.channel.unix.Errors.ioResult; import static io.netty.channel.unix.NativeInetAddress.ipv4MappedIpv6Address; import static io.netty.util.internal.ObjectUtil.checkNotNull; /** * A socket which provides access BSD native methods. */ final class BsdSocket extends Socket { // These limits are just based on observations. I couldn't find anything in header files which formally // define these limits. private static final int APPLE_SND_LOW_AT_MAX = 1 << 17; private static final int FREEBSD_SND_LOW_AT_MAX = 1 << 15; static final int BSD_SND_LOW_AT_MAX = Math.min(APPLE_SND_LOW_AT_MAX, FREEBSD_SND_LOW_AT_MAX); /** * The `endpoints` structure passed to `connectx(2)` has an optional ""source interface"" field, * which is the index of the network interface to use. * According to `if_nametoindex(3)`, the value 0 is used when no interface is specified. */ private static final int UNSPECIFIED_SOURCE_INTERFACE = 0; BsdSocket(int fd) { super(fd); } void setAcceptFilter(AcceptFilter acceptFilter) throws IOException { setAcceptFilter(intValue(), acceptFilter.filterName(), acceptFilter.filterArgs()); } void setTcpNoPush(boolean tcpNoPush) throws IOException { setTcpNoPush(intValue(), tcpNoPush ? 1 : 0); } void setSndLowAt(int lowAt) throws IOException { setSndLowAt(intValue(), lowAt); } public void setTcpFastOpen(boolean enableTcpFastOpen) throws IOException { setTcpFastOpen(intValue(), enableTcpFastOpen ? 1 : 0); } boolean isTcpNoPush() throws IOException { return getTcpNoPush(intValue()) != 0; } int getSndLowAt() throws IOException { return getSndLowAt(intValue()); } AcceptFilter getAcceptFilter() throws IOException { String[] result = getAcceptFilter(intValue()); return result == null ? PLATFORM_UNSUPPORTED : new AcceptFilter(result[0], result[1]); } public boolean isTcpFastOpen() throws IOException { return isTcpFastOpen(intValue()) != 0; } PeerCredentials getPeerCredentials() throws IOException { return getPeerCredentials(intValue()); } long sendFile(DefaultFileRegion src, long baseOffset, long offset, long length) throws IOException { // Open the file-region as it may be created via the lazy constructor. This is needed as we directly access // the FileChannel field via JNI. src.open(); long res = sendFile(intValue(), src, baseOffset, offset, length); if (res >= 0) { return res; } return ioResult(""sendfile"", (int) res); } /** * Establish a connection to the given destination address, and send the given data to it. * * Note: This method relies on the {@code connectx(2)} system call, which is MacOS specific. * * @param source the source address we are connecting from. * @param destination the destination address we are connecting to. * @param data the data to copy to the kernel-side socket buffer. * @param tcpFastOpen if {@code true}, set the flags needed to enable TCP FastOpen connecting. * @return The number of bytes copied to the kernel-side socket buffer, or the number of bytes sent to the * destination. This number is negative if connecting is left in an in-progress state, * or positive if the connection was immediately established. * @throws IOException if an IO error occurs, if the {@code data} is too big to send in one go, * or if the system call is not supported on your platform. */ int connectx(InetSocketAddress source, InetSocketAddress destination, IovArray data, boolean tcpFastOpen) throws IOException { checkNotNull(destination, ""Destination InetSocketAddress cannot be null.""); int flags = tcpFastOpen ? CONNECT_TCP_FASTOPEN : 0; boolean sourceIPv6; byte[] sourceAddress; int sourceScopeId; int sourcePort; if (source == null) { sourceIPv6 = false; sourceAddress = null; sourceScopeId = 0; sourcePort = 0; } else { InetAddress [MASK] = source.getAddress(); sourceIPv6 = useIpv6(this, [MASK] ); if ( [MASK] instanceof Inet6Address) { sourceAddress = [MASK] .getAddress(); sourceScopeId = ((Inet6Address) [MASK] ).getScopeId(); } else { // convert to ipv4 mapped ipv6 address; sourceScopeId = 0; sourceAddress = ipv4MappedIpv6Address( [MASK] .getAddress()); } sourcePort = source.getPort(); } InetAddress destinationInetAddress = destination.getAddress(); boolean destinationIPv6 = useIpv6(this, destinationInetAddress); byte[] destinationAddress; int destinationScopeId; if (destinationInetAddress instanceof Inet6Address) { destinationAddress = destinationInetAddress.getAddress(); destinationScopeId = ((Inet6Address) destinationInetAddress).getScopeId(); } else { // convert to ipv4 mapped ipv6 address; destinationScopeId = 0; destinationAddress = ipv4MappedIpv6Address(destinationInetAddress.getAddress()); } int destinationPort = destination.getPort(); long iovAddress; int iovCount; int iovDataLength; if (data == null || data.count() == 0) { iovAddress = 0; iovCount = 0; iovDataLength = 0; } else { iovAddress = data.memoryAddress(0); iovCount = data.count(); long size = data.size(); if (size > Integer.MAX_VALUE) { throw new IOException(""IovArray.size() too big: "" + size + "" bytes.""); } iovDataLength = (int) size; } int result = connectx(intValue(), UNSPECIFIED_SOURCE_INTERFACE, sourceIPv6, sourceAddress, sourceScopeId, sourcePort, destinationIPv6, destinationAddress, destinationScopeId, destinationPort, flags, iovAddress, iovCount, iovDataLength); if (result == ERRNO_EINPROGRESS_NEGATIVE) { // This is normal for non-blocking sockets. // We'll know the connection has been established when the socket is selectable for writing. // Tell the channel the data was written, so the outbound buffer can update its position. return -iovDataLength; } if (result < 0) { return ioResult(""connectx"", result); } return result; } public static BsdSocket newSocketStream() { return new BsdSocket(newSocketStream0()); } public static BsdSocket newSocketStream(InternetProtocolFamily protocol) { return new BsdSocket(newSocketStream0(protocol)); } public static BsdSocket newSocketDgram() { return new BsdSocket(newSocketDgram0()); } public static BsdSocket newSocketDgram(InternetProtocolFamily protocol) { return new BsdSocket(newSocketDgram0(protocol)); } public static BsdSocket newSocketDomain() { return new BsdSocket(newSocketDomain0()); } public static BsdSocket newSocketDomainDgram() { return new BsdSocket(newSocketDomainDgram0()); } private static native long sendFile(int socketFd, DefaultFileRegion src, long baseOffset, long offset, long length) throws IOException; /** * @return If successful, zero or positive number of bytes transfered, otherwise negative errno. */ private static native int connectx( int socketFd, // sa_endpoints_t *endpoints: int sourceInterface, boolean sourceIPv6, byte[] sourceAddress, int sourceScopeId, int sourcePort, boolean destinationIPv6, byte[] destinationAddress, int destinationScopeId, int destinationPort, // sae_associd_t associd is reserved int flags, long iovAddress, int iovCount, int iovDataLength // sae_connid_t *connid is reserved ); private static native String[] getAcceptFilter(int fd) throws IOException; private static native int getTcpNoPush(int fd) throws IOException; private static native int getSndLowAt(int fd) throws IOException; private static native int isTcpFastOpen(int fd) throws IOException; private static native PeerCredentials getPeerCredentials(int fd) throws IOException; private static native void setAcceptFilter(int fd, String filterName, String filterArgs) throws IOException; private static native void setTcpNoPush(int fd, int tcpNoPush) throws IOException; private static native void setSndLowAt(int fd, int lowAt) throws IOException; private static native void setTcpFastOpen(int fd, int enableFastOpen) throws IOException; } ","sourceInetAddress " "package com.blankj.utilcode.util; import android.content.ClipData; import android.content.Componen [MASK] ; import android.content.Intent; import android.graphics.Rect; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Formatter; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.RequiresApi; import androidx.collection.SimpleArrayMap; /** *

 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/09/21
 *     desc  : utils about log
 * 
*/ public final class LogUtils { public static final int V = Log.VERBOSE; public static final int D = Log.DEBUG; public static final int I = Log.INFO; public static final int W = Log.WARN; public static final int E = Log.ERROR; public static final int A = Log.ASSERT; @IntDef({V, D, I, W, E, A}) @Retention(RetentionPolicy.SOURCE) public @interface TYPE { } private static final char[] T = new char[]{'V', 'D', 'I', 'W', 'E', 'A'}; private static final int FILE = 0x10; private static final int JSON = 0x20; private static final int XML = 0x30; private static final String FILE_SEP = System.getProperty(""file.separator""); private static final String LINE_SEP = System.getProperty(""line.separator""); private static final String TOP_CORNER = ""┌""; private static final String MIDDLE_CORNER = ""├""; private static final String LEFT_BORDER = ""│ ""; private static final String BOTTOM_CORNER = ""└""; private static final String SIDE_DIVIDER = ""────────────────────────────────────────────────────────""; private static final String MIDDLE_DIVIDER = ""┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄""; private static final String TOP_BORDER = TOP_CORNER + SIDE_DIVIDER + SIDE_DIVIDER; private static final String MIDDLE_BORDER = MIDDLE_CORNER + MIDDLE_DIVIDER + MIDDLE_DIVIDER; private static final String BOTTOM_BORDER = BOTTOM_CORNER + SIDE_DIVIDER + SIDE_DIVIDER; private static final int MAX_LEN = 1100;// fit for Chinese character private static final String NOTHING = ""log nothing""; private static final String NULL = ""null""; private static final String ARGS = ""args""; private static final String PLACEHOLDER = "" ""; private static final Config CONFIG = new Config(); private static SimpleDateFormat simpleDateFormat; private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); private static final SimpleArrayMap I_FORMATTER_MAP = new SimpleArrayMap<>(); private LogUtils() { throw new UnsupportedOperationException(""u can't instantiate me...""); } public static Config getConfig() { return CONFIG; } public static void v(final Object... contents) { log(V, CONFIG.getGlobalTag(), contents); } public static void vTag(final String tag, final Object... contents) { log(V, tag, contents); } public static void d(final Object... contents) { log(D, CONFIG.getGlobalTag(), contents); } public static void dTag(final String tag, final Object... contents) { log(D, tag, contents); } public static void i(final Object... contents) { log(I, CONFIG.getGlobalTag(), contents); } public static void iTag(final String tag, final Object... contents) { log(I, tag, contents); } public static void w(final Object... contents) { log(W, CONFIG.getGlobalTag(), contents); } public static void wTag(final String tag, final Object... contents) { log(W, tag, contents); } public static void e(final Object... contents) { log(E, CONFIG.getGlobalTag(), contents); } public static void eTag(final String tag, final Object... contents) { log(E, tag, contents); } public static void a(final Object... contents) { log(A, CONFIG.getGlobalTag(), contents); } public static void aTag(final String tag, final Object... contents) { log(A, tag, contents); } public static void file(final Object content) { log(FILE | D, CONFIG.getGlobalTag(), content); } public static void file(@TYPE final int type, final Object content) { log(FILE | type, CONFIG.getGlobalTag(), content); } public static void file(final String tag, final Object content) { log(FILE | D, tag, content); } public static void file(@TYPE final int type, final String tag, final Object content) { log(FILE | type, tag, content); } public static void json(final Object content) { log(JSON | D, CONFIG.getGlobalTag(), content); } public static void json(@TYPE final int type, final Object content) { log(JSON | type, CONFIG.getGlobalTag(), content); } public static void json(final String tag, final Object content) { log(JSON | D, tag, content); } public static void json(@TYPE final int type, final String tag, final Object content) { log(JSON | type, tag, content); } public static void xml(final String content) { log(XML | D, CONFIG.getGlobalTag(), content); } public static void xml(@TYPE final int type, final String content) { log(XML | type, CONFIG.getGlobalTag(), content); } public static void xml(final String tag, final String content) { log(XML | D, tag, content); } public static void xml(@TYPE final int type, final String tag, final String content) { log(XML | type, tag, content); } public static void log(final int type, final String tag, final Object... contents) { if (!CONFIG.isLogSwitch()) return; final int type_low = type & 0x0f, type_high = type & 0xf0; if (CONFIG.isLog2ConsoleSwitch() || CONFIG.isLog2FileSwitch() || type_high == FILE) { if (type_low < CONFIG.mConsoleFilter && type_low < CONFIG.mFileFilter) return; final TagHead tagHead = processTagAndHead(tag); final String body = processBody(type_high, contents); if (CONFIG.isLog2ConsoleSwitch() && type_high != FILE && type_low >= CONFIG.mConsoleFilter) { print2Console(type_low, tagHead.tag, tagHead.consoleHead, body); } if ((CONFIG.isLog2FileSwitch() || type_high == FILE) && type_low >= CONFIG.mFileFilter) { EXECUTOR.execute(new Runnable() { @Override public void run() { print2File(type_low, tagHead.tag, tagHead.fileHead + body); } }); } } } public static String getCurrentLogFilePath() { return getCurrentLogFilePath(new Date()); } public static List getLogFiles() { String dir = CONFIG.getDir(); File logDir = new File(dir); if (!logDir.exists()) return new ArrayList<>(); File[] files = logDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return isMatchLogFileName(name); } }); List list = new ArrayList<>(); Collections.addAll(list, files); return list; } private static TagHead processTagAndHead(String tag) { if (!CONFIG.mTagIsSpace && !CONFIG.isLogHeadSwitch()) { tag = CONFIG.getGlobalTag(); } else { final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); final int stackIndex = 3 + CONFIG.getStackOffset(); if (stackIndex >= stackTrace.length) { StackTraceElement targetElement = stackTrace[3]; final String fileName = getFileName(targetElement); if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) { int index = fileName.indexOf('.');// Use proguard may not find '.'. tag = index == -1 ? fileName : fileName.substring(0, index); } return new TagHead(tag, null, "": ""); } StackTraceElement targetElement = stackTrace[stackIndex]; final String fileName = getFileName(targetElement); if (CONFIG.mTagIsSpace && UtilsBridge.isSpace(tag)) { int index = fileName.indexOf('.');// Use proguard may not find '.'. tag = index == -1 ? fileName : fileName.substring(0, index); } if (CONFIG.isLogHeadSwitch()) { String [MASK] = Thread.currentThread().ge [MASK] (); final String head = new Formatter() .format(""%s, %s.%s(%s:%d)"", [MASK] , targetElement.getClassName(), targetElement.getMethodName(), fileName, targetElement.getLineNumber()) .toString(); final String fileHead = "" ["" + head + ""]: ""; if (CONFIG.getStackDeep() <= 1) { return new TagHead(tag, new String[]{head}, fileHead); } else { final String[] consoleHead = new String[Math.min( CONFIG.getStackDeep(), stackTrace.length - stackIndex )]; consoleHead[0] = head; int spaceLen = [MASK] .length() + 2; String space = new Formatter().format(""%"" + spaceLen + ""s"", """").toString(); for (int i = 1, len = consoleHead.length; i < len; ++i) { targetElement = stackTrace[i + stackIndex]; consoleHead[i] = new Formatter() .format(""%s%s.%s(%s:%d)"", space, targetElement.getClassName(), targetElement.getMethodName(), getFileName(targetElement), targetElement.getLineNumber()) .toString(); } return new TagHead(tag, consoleHead, fileHead); } } } return new TagHead(tag, null, "": ""); } private static String getFileName(final StackTraceElement targetElement) { String fileName = targetElement.getFileName(); if (fileName != null) return fileName; // If name of file is null, should add // ""-keepattributes SourceFile,LineNumberTable"" in proguard file. String className = targetElement.getClassName(); String[] classNameInfo = className.split(""\\.""); if (classNameInfo.length > 0) { className = classNameInfo[classNameInfo.length - 1]; } int index = className.indexOf('$'); if (index != -1) { className = className.substring(0, index); } return className + "".java""; } private static String processBody(final int type, final Object... contents) { String body = NULL; if (contents != null) { if (contents.length == 1) { body = formatObject(type, contents[0]); } else { StringBuilder sb = new StringBuilder(); for (int i = 0, len = contents.length; i < len; ++i) { Object content = contents[i]; sb.append(ARGS) .append(""["") .append(i) .append(""]"") .append("" = "") .append(formatObject(content)) .append(LINE_SEP); } body = sb.toString(); } } return body.length() == 0 ? NOTHING : body; } private static String formatObject(int type, Object object) { if (object == null) return NULL; if (type == JSON) return LogFormatter.object2String(object, JSON); if (type == XML) return LogFormatter.object2String(object, XML); return formatObject(object); } private static String formatObject(Object object) { if (object == null) return NULL; if (!I_FORMATTER_MAP.isEmpty()) { IFormatter iFormatter = I_FORMATTER_MAP.get(getClassFromObject(object)); if (iFormatter != null) { //noinspection unchecked return iFormatter.format(object); } } return LogFormatter.object2String(object); } private static void print2Console(final int type, final String tag, final String[] head, final String msg) { if (CONFIG.isSingleTagSwitch()) { printSingleTagMsg(type, tag, processSingleTagMsg(type, tag, head, msg)); } else { printBorder(type, tag, true); printHead(type, tag, head); printMsg(type, tag, msg); printBorder(type, tag, false); } } private static void printBorder(final int type, final String tag, boolean isTop) { if (CONFIG.isLogBorderSwitch()) { print2Console(type, tag, isTop ? TOP_BORDER : BOTTOM_BORDER); } } private static void printHead(final int type, final String tag, final String[] head) { if (head != null) { for (String aHead : head) { print2Console(type, tag, CONFIG.isLogBorderSwitch() ? LEFT_BORDER + aHead : aHead); } if (CONFIG.isLogBorderSwitch()) print2Console(type, tag, MIDDLE_BORDER); } } private static void printMsg(final int type, final String tag, final String msg) { int len = msg.length(); int countOfSub = len / MAX_LEN; if (countOfSub > 0) { int index = 0; for (int i = 0; i < countOfSub; i++) { printSubMsg(type, tag, msg.substring(index, index + MAX_LEN)); index += MAX_LEN; } if (index != len) { printSubMsg(type, tag, msg.substring(index, len)); } } else { printSubMsg(type, tag, msg); } } private static void printSubMsg(final int type, final String tag, final String msg) { if (!CONFIG.isLogBorderSwitch()) { print2Console(type, tag, msg); return; } StringBuilder sb = new StringBuilder(); String[] lines = msg.split(LINE_SEP); for (String line : lines) { print2Console(type, tag, LEFT_BORDER + line); } } private static String processSingleTagMsg(final int type, final String tag, final String[] head, final String msg) { StringBuilder sb = new StringBuilder(); if (CONFIG.isLogBorderSwitch()) { sb.append(PLACEHOLDER).append(LINE_SEP); sb.append(TOP_BORDER).append(LINE_SEP); if (head != null) { for (String aHead : head) { sb.append(LEFT_BORDER).append(aHead).append(LINE_SEP); } sb.append(MIDDLE_BORDER).append(LINE_SEP); } for (String line : msg.split(LINE_SEP)) { sb.append(LEFT_BORDER).append(line).append(LINE_SEP); } sb.append(BOTTOM_BORDER); } else { if (head != null) { sb.append(PLACEHOLDER).append(LINE_SEP); for (String aHead : head) { sb.append(aHead).append(LINE_SEP); } } sb.append(msg); } return sb.toString(); } private static void printSingleTagMsg(final int type, final String tag, final String msg) { int len = msg.length(); int countOfSub = CONFIG.isLogBorderSwitch() ? (len - BOTTOM_BORDER.length()) / MAX_LEN : len / MAX_LEN; if (countOfSub > 0) { if (CONFIG.isLogBorderSwitch()) { print2Console(type, tag, msg.substring(0, MAX_LEN) + LINE_SEP + BOTTOM_BORDER); int index = MAX_LEN; for (int i = 1; i < countOfSub; i++) { print2Console(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP + LEFT_BORDER + msg.substring(index, index + MAX_LEN) + LINE_SEP + BOTTOM_BORDER); index += MAX_LEN; } if (index != len - BOTTOM_BORDER.length()) { print2Console(type, tag, PLACEHOLDER + LINE_SEP + TOP_BORDER + LINE_SEP + LEFT_BORDER + msg.substring(index, len)); } } else { print2Console(type, tag, msg.substring(0, MAX_LEN)); int index = MAX_LEN; for (int i = 1; i < countOfSub; i++) { print2Console(type, tag, PLACEHOLDER + LINE_SEP + msg.substring(index, index + MAX_LEN)); index += MAX_LEN; } if (index != len) { print2Console(type, tag, PLACEHOLDER + LINE_SEP + msg.substring(index, len)); } } } else { print2Console(type, tag, msg); } } private static void print2Console(int type, String tag, String msg) { Log.println(type, tag, msg); if (CONFIG.mOnConsoleOutputListener != null) { CONFIG.mOnConsoleOutputListener.onConsoleOutput(type, tag, msg); } } private static void print2File(final int type, final String tag, final String msg) { Date d = new Date(); String format = getSdf().format(d); String date = format.substring(0, 10); String currentLogFilePath = getCurrentLogFilePath(d); if (!createOrExistsFile(currentLogFilePath, date)) { Log.e(""LogUtils"", ""create "" + currentLogFilePath + "" failed!""); return; } String time = format.substring(11); final String content = time + T[type - V] + ""/"" + tag + msg + LINE_SEP; input2File(currentLogFilePath, content); } private static String getCurrentLogFilePath(Date d) { String format = getSdf().format(d); String date = format.substring(0, 10); return CONFIG.getDir() + CONFIG.getFilePrefix() + ""_"" + date + ""_"" + CONFIG.getProcessName() + CONFIG.getFileExtension(); } private static SimpleDateFormat getSdf() { if (simpleDateFormat == null) { simpleDateFormat = new SimpleDateFormat(""yyyy_MM_dd HH:mm:ss.SSS "", Locale.getDefault()); } return simpleDateFormat; } private static boolean createOrExistsFile(final String filePath, final String date) { File file = new File(filePath); if (file.exists()) return file.isFile(); if (!UtilsBridge.createOrExistsDir(file.getParentFile())) return false; try { deleteDueLogs(filePath, date); boolean isCreate = file.createNewFile(); if (isCreate) { printDeviceInfo(filePath, date); } return isCreate; } catch (IOException e) { e.printStackTrace(); return false; } } private static void deleteDueLogs(final String filePath, final String date) { if (CONFIG.getSaveDays() <= 0) return; File file = new File(filePath); File parentFile = file.getParentFile(); File[] files = parentFile.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return isMatchLogFileName(name); } }); if (files == null || files.length <= 0) return; final SimpleDateFormat sdf = new SimpleDateFormat(""yyyy_MM_dd"", Locale.getDefault()); try { long dueMillis = sdf.parse(date).getTime() - CONFIG.getSaveDays() * 86400000L; for (final File aFile : files) { String name = aFile.ge [MASK] (); int l = name.length(); String logDay = findDate(name); if (sdf.parse(logDay).getTime() <= dueMillis) { EXECUTOR.execute(new Runnable() { @Override public void run() { boolean delete = aFile.delete(); if (!delete) { Log.e(""LogUtils"", ""delete "" + aFile + "" failed!""); } } }); } } } catch (ParseException e) { e.printStackTrace(); } } private static boolean isMatchLogFileName(String name) { return name.matches(""^"" + CONFIG.getFilePrefix() + ""_[0-9]{4}_[0-9]{2}_[0-9]{2}_.*$""); } private static String findDate(String str) { Pattern pattern = Pattern.compile(""[0-9]{4}_[0-9]{2}_[0-9]{2}""); Matcher matcher = pattern.matcher(str); if (matcher.find()) { return matcher.group(); } return """"; } private static void printDeviceInfo(final String filePath, final String date) { CONFIG.mFileHead.addFirst(""Date of Log"", date); input2File(filePath, CONFIG.mFileHead.toString()); } private static void input2File(final String filePath, final String input) { if (CONFIG.mFileWriter == null) { UtilsBridge.writeFileFromString(filePath, input, true); } else { CONFIG.mFileWriter.write(filePath, input); } if (CONFIG.mOnFileOutputListener != null) { CONFIG.mOnFileOutputListener.onFileOutput(filePath, input); } } public static final class Config { private String mDefaultDir; // The default storage directory of log. private String mDir; // The storage directory of log. private String mFilePrefix = ""util"";// The file prefix of log. private String mFileExtension = "".txt"";// The file extension of log. private boolean mLogSwitch = true; // The switch of log. private boolean mLog2ConsoleSwitch = true; // The logcat's switch of log. private String mGlobalTag = """"; // The global tag of log. private boolean mTagIsSpace = true; // The global tag is space. private boolean mLogHeadSwitch = true; // The head's switch of log. private boolean mLog2FileSwitch = false; // The file's switch of log. private boolean mLogBorderSwitch = true; // The border's switch of log. private boolean mSingleTagSwitch = true; // The single tag of log. private int mConsoleFilter = V; // The console's filter of log. private int mFileFilter = V; // The file's filter of log. private int mStackDeep = 1; // The stack's deep of log. private int mStackOffset = 0; // The stack's offset of log. private int mSaveDays = -1; // The save days of log. private String mProcessName = UtilsBridge.getCurrentProcessName(); private IFileWriter mFileWriter; private OnConsoleOutputListener mOnConsoleOutputListener; private OnFileOutputListener mOnFileOutputListener; private UtilsBridge.FileHead mFileHead = new UtilsBridge.FileHead(""Log""); private Config() { if (UtilsBridge.isSDCardEnableByEnvironment() && Utils.getApp().getExternalFilesDir(null) != null) mDefaultDir = Utils.getApp().getExternalFilesDir(null) + FILE_SEP + ""log"" + FILE_SEP; else { mDefaultDir = Utils.getApp().getFilesDir() + FILE_SEP + ""log"" + FILE_SEP; } } public final Config setLogSwitch(final boolean logSwitch) { mLogSwitch = logSwitch; return this; } public final Config setConsoleSwitch(final boolean consoleSwitch) { mLog2ConsoleSwitch = consoleSwitch; return this; } public final Config setGlobalTag(final String tag) { if (UtilsBridge.isSpace(tag)) { mGlobalTag = """"; mTagIsSpace = true; } else { mGlobalTag = tag; mTagIsSpace = false; } return this; } public final Config setLogHeadSwitch(final boolean logHeadSwitch) { mLogHeadSwitch = logHeadSwitch; return this; } public final Config setLog2FileSwitch(final boolean log2FileSwitch) { mLog2FileSwitch = log2FileSwitch; return this; } public final Config setDir(final String dir) { if (UtilsBridge.isSpace(dir)) { mDir = null; } else { mDir = dir.endsWith(FILE_SEP) ? dir : dir + FILE_SEP; } return this; } public final Config setDir(final File dir) { mDir = dir == null ? null : (dir.getAbsolutePath() + FILE_SEP); return this; } public final Config setFilePrefix(final String filePrefix) { if (UtilsBridge.isSpace(filePrefix)) { mFilePrefix = ""util""; } else { mFilePrefix = filePrefix; } return this; } public final Config setFileExtension(final String fileExtension) { if (UtilsBridge.isSpace(fileExtension)) { mFileExtension = "".txt""; } else { if (fileExtension.startsWith(""."")) { mFileExtension = fileExtension; } else { mFileExtension = ""."" + fileExtension; } } return this; } public final Config setBorderSwitch(final boolean borderSwitch) { mLogBorderSwitch = borderSwitch; return this; } public final Config setSingleTagSwitch(final boolean singleTagSwitch) { mSingleTagSwitch = singleTagSwitch; return this; } public final Config setConsoleFilter(@TYPE final int consoleFilter) { mConsoleFilter = consoleFilter; return this; } public final Config setFileFilter(@TYPE final int fileFilter) { mFileFilter = fileFilter; return this; } public final Config setStackDeep(@IntRange(from = 1) final int stackDeep) { mStackDeep = stackDeep; return this; } public final Config setStackOffset(@IntRange(from = 0) final int stackOffset) { mStackOffset = stackOffset; return this; } public final Config setSaveDays(@IntRange(from = 1) final int saveDays) { mSaveDays = saveDays; return this; } public final Config addFormatter(final IFormatter iFormatter) { if (iFormatter != null) { I_FORMATTER_MAP.put(getTypeClassFromParadigm(iFormatter), iFormatter); } return this; } public final Config setFileWriter(final IFileWriter fileWriter) { mFileWriter = fileWriter; return this; } public final Config setOnConsoleOutputListener(final OnConsoleOutputListener listener) { mOnConsoleOutputListener = listener; return this; } public final Config setOnFileOutputListener(final OnFileOutputListener listener) { mOnFileOutputListener = listener; return this; } public final Config addFileExtraHead(final Map fileExtraHead) { mFileHead.append(fileExtraHead); return this; } public final Config addFileExtraHead(final String key, final String value) { mFileHead.append(key, value); return this; } public final String getProcessName() { if (mProcessName == null) return """"; return mProcessName.replace("":"", ""_""); } public final String getDefaultDir() { return mDefaultDir; } public final String getDir() { return mDir == null ? mDefaultDir : mDir; } public final String getFilePrefix() { return mFilePrefix; } public final String getFileExtension() { return mFileExtension; } public final boolean isLogSwitch() { return mLogSwitch; } public final boolean isLog2ConsoleSwitch() { return mLog2ConsoleSwitch; } public final String getGlobalTag() { if (UtilsBridge.isSpace(mGlobalTag)) return """"; return mGlobalTag; } public final boolean isLogHeadSwitch() { return mLogHeadSwitch; } public final boolean isLog2FileSwitch() { return mLog2FileSwitch; } public final boolean isLogBorderSwitch() { return mLogBorderSwitch; } public final boolean isSingleTagSwitch() { return mSingleTagSwitch; } public final char getConsoleFilter() { return T[mConsoleFilter - V]; } public final char getFileFilter() { return T[mFileFilter - V]; } public final int getStackDeep() { return mStackDeep; } public final int getStackOffset() { return mStackOffset; } public final int getSaveDays() { return mSaveDays; } public final boolean haveSetOnConsoleOutputListener() { return mOnConsoleOutputListener != null; } public final boolean haveSetOnFileOutputListener() { return mOnFileOutputListener != null; } @Override public String toString() { return ""process: "" + getProcessName() + LINE_SEP + ""logSwitch: "" + isLogSwitch() + LINE_SEP + ""consoleSwitch: "" + isLog2ConsoleSwitch() + LINE_SEP + ""tag: "" + (getGlobalTag().equals("""") ? ""null"" : getGlobalTag()) + LINE_SEP + ""headSwitch: "" + isLogHeadSwitch() + LINE_SEP + ""fileSwitch: "" + isLog2FileSwitch() + LINE_SEP + ""dir: "" + getDir() + LINE_SEP + ""filePrefix: "" + getFilePrefix() + LINE_SEP + ""borderSwitch: "" + isLogBorderSwitch() + LINE_SEP + ""singleTagSwitch: "" + isSingleTagSwitch() + LINE_SEP + ""consoleFilter: "" + getConsoleFilter() + LINE_SEP + ""fileFilter: "" + getFileFilter() + LINE_SEP + ""stackDeep: "" + getStackDeep() + LINE_SEP + ""stackOffset: "" + getStackOffset() + LINE_SEP + ""saveDays: "" + getSaveDays() + LINE_SEP + ""formatter: "" + I_FORMATTER_MAP + LINE_SEP + ""fileWriter: "" + mFileWriter + LINE_SEP + ""onConsoleOutputListener: "" + mOnConsoleOutputListener + LINE_SEP + ""onFileOutputListener: "" + mOnFileOutputListener + LINE_SEP + ""fileExtraHeader: "" + mFileHead.getAppended(); } } public abstract static class IFormatter { public abstract String format(T t); } public interface IFileWriter { void write(String file, String content); } public interface OnConsoleOutputListener { void onConsoleOutput(@TYPE int type, String tag, String content); } public interface OnFileOutputListener { void onFileOutput(String filePath, String content); } private final static class TagHead { String tag; String[] consoleHead; String fileHead; TagHead(String tag, String[] consoleHead, String fileHead) { this.tag = tag; this.consoleHead = consoleHead; this.fileHead = fileHead; } } private final static class LogFormatter { static String object2String(Object object) { return object2String(object, -1); } static String object2String(Object object, int type) { if (object.getClass().isArray()) return array2String(object); if (object instanceof Throwable) return UtilsBridge.getFullStackTrace((Throwable) object); if (object instanceof Bundle) return bundle2String((Bundle) object); if (object instanceof Intent) return intent2String((Intent) object); if (type == JSON) { return object2Json(object); } else if (type == XML) { return formatXml(object.toString()); } return object.toString(); } private static String bundle2String(Bundle bundle) { Iterator iterator = bundle.keySet().iterator(); if (!iterator.hasNext()) { return ""Bundle {}""; } StringBuilder sb = new StringBuilder(128); sb.append(""Bundle { ""); for (; ; ) { String key = iterator.next(); Object value = bundle.get(key); sb.append(key).append('='); if (value instanceof Bundle) { sb.append(value == bundle ? ""(this Bundle)"" : bundle2String((Bundle) value)); } else { sb.append(formatObject(value)); } if (!iterator.hasNext()) return sb.append("" }"").toString(); sb.append(',').append(' '); } } private static String intent2String(Intent intent) { StringBuilder sb = new StringBuilder(128); sb.append(""Intent { ""); boolean first = true; String mAction = intent.getAction(); if (mAction != null) { sb.append(""act="").append(mAction); first = false; } Set mCategories = intent.getCategories(); if (mCategories != null) { if (!first) { sb.append(' '); } first = false; sb.append(""cat=[""); boolean firstCategory = true; for (String c : mCategories) { if (!firstCategory) { sb.append(','); } sb.append(c); firstCategory = false; } sb.append(""]""); } Uri mData = intent.getData(); if (mData != null) { if (!first) { sb.append(' '); } first = false; sb.append(""dat="").append(mData); } String mType = intent.getType(); if (mType != null) { if (!first) { sb.append(' '); } first = false; sb.append(""typ="").append(mType); } int mFlags = intent.getFlags(); if (mFlags != 0) { if (!first) { sb.append(' '); } first = false; sb.append(""flg=0x"").append(Integer.toHexString(mFlags)); } String mPackage = intent.getPackage(); if (mPackage != null) { if (!first) { sb.append(' '); } first = false; sb.append(""pkg="").append(mPackage); } Componen [MASK] mComponent = intent.getComponent(); if (mComponent != null) { if (!first) { sb.append(' '); } first = false; sb.append(""cmp="").append(mComponent.flattenToShortString()); } Rect mSourceBounds = intent.getSourceBounds(); if (mSourceBounds != null) { if (!first) { sb.append(' '); } first = false; sb.append(""bnds="").append(mSourceBounds.toShortString()); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { ClipData mClipData = intent.getClipData(); if (mClipData != null) { if (!first) { sb.append(' '); } first = false; clipData2String(mClipData, sb); } } Bundle mExtras = intent.getExtras(); if (mExtras != null) { if (!first) { sb.append(' '); } first = false; sb.append(""extras={""); sb.append(bundle2String(mExtras)); sb.append('}'); } if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { Intent mSelector = intent.getSelector(); if (mSelector != null) { if (!first) { sb.append(' '); } first = false; sb.append(""sel={""); sb.append(mSelector == intent ? ""(this Intent)"" : intent2String(mSelector)); sb.append(""}""); } } sb.append("" }""); return sb.toString(); } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) private static void clipData2String(ClipData clipData, StringBuilder sb) { ClipData.Item item = clipData.getItemAt(0); if (item == null) { sb.append(""ClipData.Item {}""); return; } sb.append(""ClipData.Item { ""); String mHtmlText = item.getHtmlText(); if (mHtmlText != null) { sb.append(""H:""); sb.append(mHtmlText); sb.append(""}""); return; } CharSequence mText = item.getText(); if (mText != null) { sb.append(""T:""); sb.append(mText); sb.append(""}""); return; } Uri uri = item.getUri(); if (uri != null) { sb.append(""U:"").append(uri); sb.append(""}""); return; } Intent intent = item.getIntent(); if (intent != null) { sb.append(""I:""); sb.append(intent2String(intent)); sb.append(""}""); return; } sb.append(""NULL""); sb.append(""}""); } private static String object2Json(Object object) { if (object instanceof CharSequence) { return UtilsBridge.formatJson(object.toString()); } try { return UtilsBridge.getGson4LogUtils().toJson(object); } catch (Throwable t) { return object.toString(); } } private static String formatJson(String json) { try { for (int i = 0, len = json.length(); i < len; i++) { char c = json.charAt(i); if (c == '{') { return new JSONObject(json).toString(2); } else if (c == '[') { return new JSONArray(json).toString(2); } else if (!Character.isWhitespace(c)) { return json; } } } catch (JSONException e) { e.printStackTrace(); } return json; } private static String formatXml(String xml) { try { Source xmlInput = new StreamSource(new StringReader(xml)); StreamResult xmlOutput = new StreamResult(new StringWriter()); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, ""yes""); transformer.setOutputProperty(""{http://xml.apache.org/xslt}indent-amount"", ""2""); transformer.transform(xmlInput, xmlOutput); xml = xmlOutput.getWriter().toString().replaceFirst("">"", "">"" + LINE_SEP); } catch (Exception e) { e.printStackTrace(); } return xml; } private static String array2String(Object object) { if (object instanceof Object[]) { return Arrays.deepToString((Object[]) object); } else if (object instanceof boolean[]) { return Arrays.toString((boolean[]) object); } else if (object instanceof byte[]) { return Arrays.toString((byte[]) object); } else if (object instanceof char[]) { return Arrays.toString((char[]) object); } else if (object instanceof double[]) { return Arrays.toString((double[]) object); } else if (object instanceof float[]) { return Arrays.toString((float[]) object); } else if (object instanceof int[]) { return Arrays.toString((int[]) object); } else if (object instanceof long[]) { return Arrays.toString((long[]) object); } else if (object instanceof short[]) { return Arrays.toString((short[]) object); } throw new IllegalArgumentException(""Array has incompatible type: "" + object.getClass()); } } private static Class getTypeClassFromParadigm(final IFormatter formatter) { Type[] genericInterfaces = formatter.getClass().getGenericInterfaces(); Type type; if (genericInterfaces.length == 1) { type = genericInterfaces[0]; } else { type = formatter.getClass().getGenericSuperclass(); } type = ((ParameterizedType) type).getActualTypeArguments()[0]; while (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getRawType(); } String className = type.toString(); if (className.startsWith(""class "")) { className = className.substring(6); } else if (className.startsWith(""interface "")) { className = className.substring(10); } try { return Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } private static Class getClassFromObject(final Object obj) { Class objClass = obj.getClass(); if (objClass.isAnonymousClass() || objClass.isSynthetic()) { Type[] genericInterfaces = objClass.getGenericInterfaces(); String className; if (genericInterfaces.length == 1) {// interface Type type = genericInterfaces[0]; while (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getRawType(); } className = type.toString(); } else {// abstract class or lambda Type type = objClass.getGenericSuperclass(); while (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getRawType(); } className = type.toString(); } if (className.startsWith(""class "")) { className = className.substring(6); } else if (className.startsWith(""interface "")) { className = className.substring(10); } try { return Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return objClass; } } ","tName " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.reactive; import org.apache.dubbo.reactive.handler.OneToManyMethodHandler; import org.apache.dubbo.rpc.protocol.tri.observer.ServerCallToObserverAdapter; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import reactor.core.publisher.Flux; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; /** * Unit test for OneToManyMethodHandler */ public final class OneToManyMethodHandlerTest { @Test void testInvoke() { String request = ""1,2,3,4,5,6,7""; AtomicInteger nextCounter = new AtomicInteger(); AtomicInteger completeCounter = new AtomicInteger(); AtomicInteger errorCounter = new AtomicInteger(); ServerCallToObserverAdapter responseObserver = Mockito.mock(ServerCallToObserverAdapter.class); doAnswer(o -> nextCounter.incrementAndGet()) .when(responseObserver).onNext(anyString()); doAnswer(o -> completeCounter.incrementAndGet()) .when(responseObserver).onCompleted(); doAnswer(o -> errorCounter.incrementAndGet()) .when(responseObserver).onError(any(Throwable.class)); OneToManyMethodHandler handler = new OneToManyMethodHandler<>(requestMono -> requestMono.flatMapMany(r -> Flux.fromArray(r.split("","")))); CompletableFuture [MASK] = handler.invoke(new Object[]{request, responseObserver}); Assertions.assertTrue( [MASK] .isDone()); Assertions.assertEquals(7, nextCounter.get()); Assertions.assertEquals(0, errorCounter.get()); Assertions.assertEquals(1, completeCounter.get()); } @Test void testError() { String request = ""1,2,3,4,5,6,7""; AtomicInteger nextCounter = new AtomicInteger(); AtomicInteger completeCounter = new AtomicInteger(); AtomicInteger errorCounter = new AtomicInteger(); ServerCallToObserverAdapter responseObserver = Mockito.mock(ServerCallToObserverAdapter.class); doAnswer(o -> nextCounter.incrementAndGet()) .when(responseObserver).onNext(anyString()); doAnswer(o -> completeCounter.incrementAndGet()) .when(responseObserver).onCompleted(); doAnswer(o -> errorCounter.incrementAndGet()) .when(responseObserver).onError(any(Throwable.class)); OneToManyMethodHandler handler = new OneToManyMethodHandler<>(requestMono -> Flux.create(emitter -> { for (int i = 0; i < 10; i++) { if (i == 6) { emitter.error(new Throwable()); } else { emitter.next(String.valueOf(i)); } } })); CompletableFuture [MASK] = handler.invoke(new Object[]{request, responseObserver}); Assertions.assertTrue( [MASK] .isDone()); Assertions.assertEquals(6, nextCounter.get()); Assertions.assertEquals(1, errorCounter.get()); Assertions.assertEquals(0, completeCounter.get()); } } ","future " "/* * The MIT License * * Copyright (c) 2018, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the ""Software""), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package jenkins.util; import hudson.Util; import java.util.HashMap; import java.util.Map; /** * Utilities to reduce memory footprint * @author Sam Van Oort */ public class MemoryReductionUtil { /** Returns the capacity we need to allocate for a HashMap so it will hold all elements without needing to resize. */ public static int preallocatedHashmapCapacity(int [MASK] ) { if ( [MASK] <= 0) { return 0; } else if ( [MASK] < 3) { return [MASK] + 1; } else { return [MASK] + [MASK] / 3; // Default load factor is 0.75, so we want to fill that much. } } /** Returns a mutable HashMap presized to hold the given number of elements without needing to resize. */ public static Map getPresizedMutableMap(int elementCount) { return new HashMap(preallocatedHashmapCapacity(elementCount)); } /** Empty string array, exactly what it says on the tin. Avoids repeatedly created empty array when calling ""toArray."" */ public static final String[] EMPTY_STRING_ARRAY = new String[0]; /** Returns the input strings, but with all values interned. */ public static String[] internInPlace(String[] input) { if (input == null) { return null; } else if (input.length == 0) { return EMPTY_STRING_ARRAY; } for (int i = 0; i < input.length; i++) { input[i] = Util.intern(input[i]); } return input; } } ","elementsToHold " "// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.analysis; import static com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper.attributeOrNull; import com.google.devtools.build.lib.analysis.AliasProvider.TargetMode; import com.google.devtools.build.lib.analysis.RuleContext.PrerequisiteValidator; import com.google.devtools.build.lib.analysis.configuredtargets.PackageGroupConfiguredTarget; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.FunctionSplitTransitionAllowlist; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; import com.google.devtools.build.lib.packages.RawAttributeMapper; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; /** * A base implementation of {@link PrerequisiteValidator} that performs common checks based on * definitions of what is considered the same logical package and what is considered ""experimental"" * code, which has may have relaxed checks for visibility and deprecation. */ public abstract class CommonPrerequisiteValidator implements PrerequisiteValidator { @Override public void validate( RuleContext.Builder [MASK] Builder, ConfiguredTargetAndData prerequisite, Attribute attribute) { validateDirectPrerequisiteLocation( [MASK] Builder, prerequisite); validateDirectPrerequisiteVisibility( [MASK] Builder, prerequisite, attribute); validateDirectPrerequisiteForTestOnly( [MASK] Builder, prerequisite); validateDirectPrerequisiteForDeprecation( [MASK] Builder, [MASK] Builder.getRule(), prerequisite, [MASK] Builder.forAspect()); } /** * Returns whether two packages are considered the same for purposes of deprecation warnings. * Dependencies within the same package do not print deprecation warnings; a package in the * javatests directory may also depend on its corresponding java package without a warning. */ public abstract boolean isSameLogicalPackage( PackageIdentifier thisPackage, PackageIdentifier prerequisitePackage); /** * Returns whether a package is considered experimental. Packages outside of experimental may not * depend on packages that are experimental. */ protected abstract boolean packageUnderExperimental(PackageIdentifier packageIdentifier); protected abstract boolean checkVisibilityForExperimental(RuleContext.Builder [MASK] ); protected abstract boolean checkVisibilityForToolchains( RuleContext.Builder [MASK] , Label prerequisite); protected abstract boolean allowExperimentalDeps(RuleContext.Builder [MASK] ); private void validateDirectPrerequisiteVisibility( RuleContext.Builder [MASK] , ConfiguredTargetAndData prerequisite, Attribute attribute) { String attrName = attribute.getName(); Rule rule = [MASK] .getRule(); checkVisibilityAttributeContents( [MASK] , prerequisite, attribute, attrName, rule); if (isSameLogicalPackage( rule.getLabel().getPackageIdentifier(), AliasProvider.getDependencyLabel(prerequisite.getConfiguredTarget()) .getPackageIdentifier())) { return; } // We don't check the visibility of late-bound attributes, because it would break some // features. if (Attribute.isLateBound(attrName)) { return; } // Determine whether we should check toolchain target visibility. if (attrName.equals(RuleContext.TOOLCHAIN_ATTR_NAME) && !checkVisibilityForToolchains( [MASK] , prerequisite.getTargetLabel())) { return; } // Determine if we should use the new visibility rules for tools. boolean toolCheckAtDefinition = [MASK] .getStarlarkSemantics() .getBool(BuildLanguageOptions.INCOMPATIBLE_VISIBILITY_PRIVATE_ATTRIBUTES_AT_DEFINITION); if (!toolCheckAtDefinition || !attribute.isImplicit() || attribute.getName().equals(RuleClass.CONFIG_SETTING_DEPS_ATTRIBUTE) || rule.getRuleClassObject().getRuleDefinitionEnvironmentLabel() == null) { // Default check: The attribute must be visible from the target. if (! [MASK] .isVisible(prerequisite.getConfiguredTarget())) { handleVisibilityConflict( [MASK] , prerequisite, rule.getLabel()); } } else { // For implicit attributes, check if the prerequisite is visible from the location of the // rule definition Label implicitDefinition = rule.getRuleClassObject().getRuleDefinitionEnvironmentLabel(); if (!RuleContext.isVisible(implicitDefinition, prerequisite.getConfiguredTarget())) { handleVisibilityConflict( [MASK] , prerequisite, implicitDefinition); } } } private void checkVisibilityAttributeContents( RuleContext.Builder [MASK] , ConfiguredTargetAndData prerequisite, Attribute attribute, String attrName, Rule rule) { if (prerequisite.getConfiguredTarget().unwrapIfMerged() instanceof PackageGroupConfiguredTarget) { Attribute configuredAttribute = RawAttributeMapper.of(rule).getAttributeDefinition(attrName); if (configuredAttribute == null) { // handles aspects configuredAttribute = attribute; } String description = configuredAttribute.getRequiredProviders().getDescription(); boolean containsPackageSpecificationProvider = description.contains(""PackageSpecificationProvider"") || description.contains(""PackageSpecificationInfo""); // TODO(plf): Add the PackageSpecificationProvider to the 'visibility' attribute. if (!attrName.equals(""visibility"") && !attrName.equals(FunctionSplitTransitionAllowlist.ATTRIBUTE_NAME) && !attrName.equals(FunctionSplitTransitionAllowlist.LEGACY_ATTRIBUTE_NAME) && !containsPackageSpecificationProvider) { [MASK] .attributeError( attrName, ""in "" + attrName + "" attribute of "" + rule.getRuleClass() + "" rule "" + rule.getLabel() + "": "" + AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITH_KIND) + "" is misplaced here (they are only allowed in the visibility attribute)""); } } } private void handleVisibilityConflict( RuleContext.Builder [MASK] , ConfiguredTargetAndData prerequisite, Label rule) { if (packageUnderExperimental(rule.getPackageIdentifier()) && !checkVisibilityForExperimental( [MASK] )) { return; } if (! [MASK] .getConfiguration().checkVisibility()) { String errorMessage = String.format( ""Target '%s' violates visibility of "" + ""%s. Continuing because --nocheck_visibility is active"", rule, AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITHOUT_KIND)); [MASK] .ruleWarning(errorMessage); } else { String errorMessage = String.format( ""%s is not visible from target '%s'. Check "" + ""the visibility declaration of the former target if you think "" + ""the dependency is legitimate"", AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITHOUT_KIND), rule); if (prerequisite.getTargetKind().equals(InputFile.targetKind())) { errorMessage += "". To set the visibility of that source file target, use the exports_files() function""; } [MASK] .ruleError(errorMessage); } } private void validateDirectPrerequisiteLocation( RuleContext.Builder [MASK] , ConfiguredTargetAndData prerequisite) { Rule rule = [MASK] .getRule(); Label prerequisiteLabel = prerequisite.getTargetLabel(); if (packageUnderExperimental(prerequisiteLabel.getPackageIdentifier()) && !packageUnderExperimental(rule.getLabel().getPackageIdentifier())) { String message = ""non-experimental target '"" + rule.getLabel() + ""' depends on experimental target '"" + prerequisiteLabel + ""'""; if (allowExperimentalDeps( [MASK] )) { [MASK] .ruleWarning( message + "" (ignored due to --experimental_deps_ok;"" + "" do not submit)""); } else { [MASK] .ruleError( message + "" (you may not check in such a dependency,"" + "" though you can test "" + ""against it by passing --experimental_deps_ok)""); } } } /** Checks if the given prerequisite is deprecated and prints a warning if so. */ private void validateDirectPrerequisiteForDeprecation( RuleErrorConsumer errors, Rule rule, ConfiguredTargetAndData prerequisite, boolean forAspect) { if (forAspect || attributeOrNull(rule, ""deprecation"", Type.STRING) != null) { // No warning for aspects because the base target would already have the warning. // No warning if the current target is already deprecated. return; } String warning = prerequisite.getDeprecationWarning(); if (warning == null) { return; // No warning if it's not deprecated. } PackageIdentifier thisPackage = rule.getLabel().getPackageIdentifier(); Label prerequisiteLabel = prerequisite.getTargetLabel(); PackageIdentifier thatPackage = prerequisiteLabel.getPackageIdentifier(); if (isSameLogicalPackage(thisPackage, thatPackage)) { return; // Doesn't report deprecation edges within a package. } Label generatingRuleLabel = prerequisite.getGeneratingRuleLabel(); if (generatingRuleLabel != null) { errors.ruleWarning( ""target '"" + rule.getLabel() + ""' depends on the output file "" + prerequisiteLabel + "" of a deprecated rule "" + generatingRuleLabel + ""': "" + warning); } else { errors.ruleWarning( ""target '"" + rule.getLabel() + ""' depends on deprecated target '"" + prerequisiteLabel + ""': "" + warning); } } /** Check that the dependency is not test-only, or the current rule is test-only. */ private void validateDirectPrerequisiteForTestOnly( RuleContext.Builder [MASK] , ConfiguredTargetAndData prerequisite) { Rule rule = [MASK] .getRule(); if (rule.getRuleClassObject().getAdvertisedProviders().canHaveAnyProvider()) { // testonly-ness will be checked directly between the depender and the target of the alias; // getTarget() called by the depender will not return the alias rule, but its actual target return; } if (!prerequisite.isTestOnly() || isTestOnlyRule(rule)) { return; } String message; Label generatingRuleLabel = prerequisite.getGeneratingRuleLabel(); if (generatingRuleLabel == null) { message = ""non-test target '"" + rule.getLabel() + ""' depends on testonly "" + AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITHOUT_KIND) + "" and doesn't have testonly attribute set""; } else if ( [MASK] .getConfiguration().checkTestonlyForOutputFiles()) { message = ""non-test target '"" + rule.getLabel() + ""' depends on the output file "" + AliasProvider.describeTargetWithAliases(prerequisite, TargetMode.WITHOUT_KIND) + "" of a testonly rule "" + generatingRuleLabel + "" and doesn't have testonly attribute set""; } else { return; } PackageIdentifier thisPackage = rule.getLabel().getPackageIdentifier(); if (packageUnderExperimental(thisPackage)) { [MASK] .ruleWarning(message); } else { [MASK] .ruleError(message); } } private static boolean isTestOnlyRule(Rule rule) { NonconfigurableAttributeMapper mapper = NonconfigurableAttributeMapper.of(rule); return mapper.has(""testonly"", Type.BOOLEAN) && mapper.get(""testonly"", Type.BOOLEAN); } } ","context " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.source; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import android.net.Uri; import android.os.Looper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManagerProvider; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.errorprone.annotations.CanIgnoreReturnValue; /** * Provides one period that loads data from a {@link Uri} and extracted using an {@link Extractor}. * *

If the possible input stream container formats are known, pass a factory that instantiates * extractors for them to the constructor. Otherwise, pass a {@link DefaultExtractorsFactory} to use * the default extractors. When reading a new stream, the first {@link Extractor} in the array of * extractors created by the factory that returns {@code true} from {@link Extractor#sniff} will be * used to extract samples from the input stream. * *

Note that the built-in extractor for FLV streams does not support seeking. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public final class ProgressiveMediaSource extends BaseMediaSource implements ProgressiveMediaPeriod.Listener { /** Factory for {@link ProgressiveMediaSource}s. */ @SuppressWarnings(""deprecation"") // Implement deprecated type for backwards compatibility. public static final class Factory implements MediaSourceFactory { private final DataSource.Factory dataSourceFactory; private ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory; private DrmSessionManagerProvider drmSessionManagerProvider; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private int continueLoadingCheckIntervalBytes; /** * Creates a new factory for {@link ProgressiveMediaSource}s. * *

The factory will use the following default components: * *

    *
  • {@link DefaultExtractorsFactory} *
  • {@link DefaultDrmSessionManagerProvider} *
  • {@link DefaultLoadErrorHandlingPolicy} *
* * @param dataSourceFactory A factory for {@linkplain DataSource data sources} to read the * media. */ public Factory(DataSource.Factory dataSourceFactory) { this(dataSourceFactory, new DefaultExtractorsFactory()); } /** * Equivalent to {@link #Factory(DataSource.Factory, ProgressiveMediaExtractor.Factory) new * Factory(dataSourceFactory, () -> new BundledExtractorsAdapter(extractorsFactory)}. * *

The factory will use the following default components: * *

    *
  • {@link DefaultDrmSessionManagerProvider} *
  • {@link DefaultLoadErrorHandlingPolicy} *
* * @param dataSourceFactory A factory for {@linkplain DataSource data sources} to read the * media. * @param extractorsFactory A factory for the {@linkplain Extractor extractors} used to extract * the media from its container. */ public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { this(dataSourceFactory, playerId -> new BundledExtractorsAdapter(extractorsFactory)); } /** * Creates a new factory for {@link ProgressiveMediaSource}s. * *

The factory will use the following default components: * *

    *
  • {@link DefaultDrmSessionManagerProvider} *
  • {@link DefaultLoadErrorHandlingPolicy} *
* * @param dataSourceFactory A factory for {@linkplain DataSource data sources} to read the * media. * @param progressiveMediaExtractorFactory A factory for the {@link ProgressiveMediaExtractor} * to extract the media from its container. */ public Factory( DataSource.Factory dataSourceFactory, ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory) { this( dataSourceFactory, progressiveMediaExtractorFactory, new DefaultDrmSessionManagerProvider(), new DefaultLoadErrorHandlingPolicy(), DEFAULT_LOADING_CHECK_INTERVAL_BYTES); } /** * Creates a new factory for {@link ProgressiveMediaSource}s. * * @param dataSourceFactory A factory for {@linkplain DataSource data sources} to read the * media. * @param progressiveMediaExtractorFactory A factory for the {@link ProgressiveMediaExtractor} * to extract media from its container. * @param drmSessionManagerProvider A provider to obtain a {@link DrmSessionManager} for a * {@link MediaItem}. * @param loadErrorHandlingPolicy A policy to handle load error. * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between * each invocation of {@link * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. */ public Factory( DataSource.Factory dataSourceFactory, ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory, DrmSessionManagerProvider drmSessionManagerProvider, LoadErrorHandlingPolicy loadErrorHandlingPolicy, int continueLoadingCheckIntervalBytes) { this.dataSourceFactory = dataSourceFactory; this.progressiveMediaExtractorFactory = progressiveMediaExtractorFactory; this.drmSessionManagerProvider = drmSessionManagerProvider; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; } @CanIgnoreReturnValue @Override public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) { this.loadErrorHandlingPolicy = checkNotNull( loadErrorHandlingPolicy, ""MediaSource.Factory#setLoadErrorHandlingPolicy no longer handles null by"" + "" instantiating a new DefaultLoadErrorHandlingPolicy. Explicitly construct and"" + "" pass an instance in order to retain the old behavior.""); return this; } /** * Sets the number of bytes that should be loaded between each invocation of {@link * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. The default value is * {@link #DEFAULT_LOADING_CHECK_INTERVAL_BYTES}. * * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between * each invocation of {@link * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. * @return This factory, for convenience. */ @CanIgnoreReturnValue public Factory setContinueLoadingCheckIntervalBytes(int continueLoadingCheckIntervalBytes) { this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; return this; } @CanIgnoreReturnValue @Override public Factory setDrmSessionManagerProvider( DrmSessionManagerProvider drmSessionManagerProvider) { this.drmSessionManagerProvider = checkNotNull( drmSessionManagerProvider, ""MediaSource.Factory#setDrmSessionManagerProvider no longer handles null by"" + "" instantiating a new DefaultDrmSessionManagerProvider. Explicitly construct"" + "" and pass an instance in order to retain the old behavior.""); return this; } /** * Returns a new {@link ProgressiveMediaSource} using the current parameters. * * @param mediaItem The {@link MediaItem}. * @return The new {@link ProgressiveMediaSource}. * @throws NullPointerException if {@link MediaItem#localConfiguration} is {@code null}. */ @Override public ProgressiveMediaSource createMediaSource(MediaItem mediaItem) { checkNotNull(mediaItem.localConfiguration); return new ProgressiveMediaSource( mediaItem, dataSourceFactory, progressiveMediaExtractorFactory, drmSessionManagerProvider.get(mediaItem), loadErrorHandlingPolicy, continueLoadingCheckIntervalBytes); } @Override public @C.ContentType int[] getSupportedTypes() { return new int[] {C.CONTENT_TYPE_OTHER}; } } /** * The default number of bytes that should be loaded between each each invocation of {@link * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. */ public static final int DEFAULT_LOADING_CHECK_INTERVAL_BYTES = 1024 * 1024; private final MediaItem mediaItem; private final MediaItem.LocalConfiguration localConfiguration; private final DataSource.Factory dataSourceFactory; private final ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory; private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy; private final int continueLoadingCheckIntervalBytes; private boolean timelineIsPlaceholder; private long timelineDurationUs; private boolean timelineIsSeekable; private boolean timelineIsLive; @Nullable private TransferListener transferListener; private ProgressiveMediaSource( MediaItem mediaItem, DataSource.Factory dataSourceFactory, ProgressiveMediaExtractor.Factory progressiveMediaExtractorFactory, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy, int continueLoadingCheckIntervalBytes) { this.localConfiguration = checkNotNull(mediaItem.localConfiguration); this.mediaItem = mediaItem; this.dataSourceFactory = dataSourceFactory; this.progressiveMediaExtractorFactory = progressiveMediaExtractorFactory; this.drmSessionManager = drmSessionManager; this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.timelineIsPlaceholder = true; this.timelineDurationUs = C.TIME_UNSET; } @Override public MediaItem getMediaItem() { return mediaItem; } @Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { transferListener = mediaTransferListener; drmSessionManager.setPlayer( /* playbackLooper= */ checkNotNull(Looper.myLooper()), getPlayerId()); drmSessionManager.prepare(); notifySourceInfoRefreshed(); } @Override public void maybeThrowSourceInfoRefreshError() { // Do nothing. } @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { DataSource dataSource = dataSourceFactory.createDataSource(); if (transferListener != null) { dataSource.addTransferListener(transferListener); } return new ProgressiveMediaPeriod( localConfiguration.uri, dataSource, progressiveMediaExtractorFactory.createProgressiveMediaExtractor(getPlayerId()), drmSessionManager, createDrmEventDispatcher(id), loadableLoadErrorHandlingPolicy, createEventDispatcher(id), this, allocator, localConfiguration.customCacheKey, continueLoadingCheckIntervalBytes); } @Override public void releasePeriod(MediaPeriod mediaPeriod) { ((ProgressiveMediaPeriod) mediaPeriod).release(); } @Override protected void releaseSourceInternal() { drmSessionManager.release(); } // ProgressiveMediaPeriod.Listener implementation. @Override public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) { // If we already have the duration from a previous source info refresh, use it. durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs; if (!timelineIsPlaceholder && timelineDurationUs == durationUs && timelineIsSeekable == isSeekable && timelineIsLive == isLive) { // Suppress no-op source info changes. return; } timelineDurationUs = durationUs; timelineIsSeekable = isSeekable; timelineIsLive = isLive; timelineIsPlaceholder = false; notifySourceInfoRefreshed(); } // Internal methods. private void notifySourceInfoRefreshed() { // TODO: Split up isDynamic into multiple fields to indicate which values may change. Then // indicate that the duration may change until it's known. See [internal: b/69703223]. Timeline timeline = new SinglePeriodTimeline( timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, /* useLiveConfiguration= */ timelineIsLive, /* manifest= */ null, mediaItem); if (timelineIsPlaceholder) { // TODO: Actually prepare the extractors during preparation so that we don't need a // placeholder. See https://github.com/google/ExoPlayer/issues/4727. timeline = new ForwardingTimeline(timeline) { @Override public Window getWindow( int windowIndex, Window window, long [MASK] ) { super.getWindow(windowIndex, window, [MASK] ); window.isPlaceholder = true; return window; } @Override public Period getPeriod(int periodIndex, Period period, boolean setIds) { super.getPeriod(periodIndex, period, setIds); period.isPlaceholder = true; return period; } }; } refreshSourceInfo(timeline); } } ","defaultPositionProjectionUs " "/* * The MIT License * * Copyright (c) 2008-2011, Sun Microsystems, Inc., Alan Harder, Jerome Lacoste, Kohsuke Kawaguchi, * bap2000, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the ""Software""), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package executable; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.MissingResourceException; import java.util.NavigableSet; import java.util.TreeSet; import java.util.UUID; import java.util.jar.JarFile; import java.util.jar.Manifest; /** * Launcher class for stand-alone execution of Jenkins as * {@code java -jar jenkins.war}. * *

On a high-level architectural note, this class is intended to be a very thin wrapper whose * primary purpose is to extract Winstone and delegate to Winstone's own initialization mechanism. * The logic in this class should only perform Jenkins-specific argument and environment validation * and Jenkins-specific Winstone customization prior to delegating to Winstone. * *

In particular, managing the logging subsystem is completely delegated to Winstone, and this * class should neither assume that logging has been initialized nor take advantage of the logging * subsystem. In the event that this class needs to print information to the user, it should do so * via the standard output (stdout) and standard error (stderr) streams rather than via the logging * subsystem. Such messages should generally be avoided except for fatal scenarios, such as an * inappropriate Java Virtual Machine (JVM) or some other serious failure that would preclude * starting Winstone. * * @author Kohsuke Kawaguchi */ public class Main { private static final NavigableSet SUPPORTED_JAVA_VERSIONS = new TreeSet<>(Arrays.asList(11, 17, 21)); /** * Sets custom session cookie name. * It may be used to prevent randomization of JSESSIONID cookies and issues like * JENKINS-25046. * @since 2.66 */ private static final String JSESSIONID_COOKIE_NAME = System.getProperty(""executableWar.jetty.sessionIdCookieName""); /** * Disables usage of the custom cookie names when starting the WAR file. * If the flag is specified, the session ID will be defined by the internal Jetty logic. * In such case it becomes configurable via * Jetty XML Config file> * or via system properties. * @since 2.66 */ private static final boolean DISABLE_CUSTOM_JSESSIONID_COOKIE_NAME = Boolean.getBoolean(""executableWar.jetty.disableCustomSessionIdCookieName""); /** * Flag to bypass the Java [MASK] check when starting. */ private static final String ENABLE_FUTURE_JAVA_CLI_SWITCH = ""--enable-future-java""; /*package*/ static void verifyJavaVersion(int releaseVersion, boolean enableFutureJava) { if (SUPPORTED_JAVA_VERSIONS.contains(releaseVersion)) { // Great! } else if (releaseVersion >= SUPPORTED_JAVA_VERSIONS.first()) { if (enableFutureJava) { System.err.println( String.format( ""Running with Java %d from %s, which is not fully supported. "" + ""Continuing because %s is set. "" + ""Supported Java [MASK] s are: %s. "" + ""See https://jenkins.io/redirect/java-support/ for more information."", releaseVersion, System.getProperty(""java.home""), ENABLE_FUTURE_JAVA_CLI_SWITCH, SUPPORTED_JAVA_VERSIONS)); } else if (releaseVersion > SUPPORTED_JAVA_VERSIONS.last()) { throw new UnsupportedClassVersionError( String.format( ""Running with Java %d from %s, which is not yet fully supported.%n"" + ""Run the command again with the %s flag to enable preview support for future Java [MASK] s.%n"" + ""Supported Java [MASK] s are: %s"", releaseVersion, System.getProperty(""java.home""), ENABLE_FUTURE_JAVA_CLI_SWITCH, SUPPORTED_JAVA_VERSIONS)); } else { throw new UnsupportedClassVersionError( String.format( ""Running with Java %d from %s, which is not fully supported.%n"" + ""Run the command again with the %s flag to bypass this error.%n"" + ""Supported Java [MASK] s are: %s"", releaseVersion, System.getProperty(""java.home""), ENABLE_FUTURE_JAVA_CLI_SWITCH, SUPPORTED_JAVA_VERSIONS)); } } else { throw new UnsupportedClassVersionError( String.format( ""Running with Java %d from %s, which is older than the minimum required [MASK] (Java %d).%n"" + ""Supported Java [MASK] s are: %s"", releaseVersion, System.getProperty(""java.home""), SUPPORTED_JAVA_VERSIONS.first(), SUPPORTED_JAVA_VERSIONS)); } } /** * Get the release [MASK] of the current JVM. * * @return The release [MASK] of the current JVM; e.g., 8, 11, or 17. * @throws NumberFormatException If the release [MASK] could not be parsed. */ private static int getReleaseVersion() { String [MASK] = System.getProperty(""java.specification. [MASK] ""); [MASK] = [MASK] .trim(); if ( [MASK] .startsWith(""1."")) { String[] split = [MASK] .split(""\\.""); if (split.length != 2) { throw new NumberFormatException(""Invalid Java specification [MASK] : "" + [MASK] ); } [MASK] = split[1]; } return Integer.parseInt( [MASK] ); } /** * Returns true if the Java runtime [MASK] check should not be done, and any [MASK] allowed. * * @see #ENABLE_FUTURE_JAVA_CLI_SWITCH */ private static boolean isFutureJavaEnabled(String[] args) { return hasArgument(ENABLE_FUTURE_JAVA_CLI_SWITCH, args) || Boolean.parseBoolean(System.getenv(""JENKINS_ENABLE_FUTURE_JAVA"")); } // TODO: Rework everything to use List private static boolean hasArgument(@NonNull String argument, @NonNull String[] args) { for (String arg : args) { if (argument.equals(arg)) { return true; } } return false; } @SuppressFBWarnings( value = ""PATH_TRAVERSAL_IN"", justification = ""User provided values for running the program"") public static void main(String[] args) throws IllegalAccessException { try { verifyJavaVersion(getReleaseVersion(), isFutureJavaEnabled(args)); } catch (UnsupportedClassVersionError e) { System.err.println(e.getMessage()); System.err.println(""See https://jenkins.io/redirect/java-support/ for more information.""); System.exit(1); } //Allows to pass arguments through stdin to ""hide"" sensitive parameters like httpsKeyStorePassword //to achieve this use --paramsFromStdIn if (hasArgument(""--paramsFromStdIn"", args)) { System.out.println(""--paramsFromStdIn detected. Parameters are going to be read from stdin. Other parameters passed directly will be ignored.""); String argsInStdIn = readStringNonBlocking(System.in, 131072).trim(); args = argsInStdIn.split("" +""); } // If someone just wants to know the [MASK] , print it out as soon as possible, with no extraneous file or webroot info. // This makes it easier to grab the [MASK] from a script final List arguments = new ArrayList<>(Arrays.asList(args)); if (arguments.contains(""-- [MASK] "")) { System.out.println(getVersion(""?"")); return; } File extractedFilesFolder = null; for (String arg : args) { if (arg.startsWith(""--extractedFilesFolder="")) { extractedFilesFolder = new File(arg.substring(""--extractedFilesFolder="".length())); if (!extractedFilesFolder.isDirectory()) { System.err.println(""The extractedFilesFolder value is not a directory. Ignoring.""); extractedFilesFolder = null; } } } for (String arg : args) { if (arg.startsWith(""--pluginroot="")) { System.setProperty(""hudson.PluginManager.workDir"", new File(arg.substring(""--pluginroot="".length())).getAbsolutePath()); // if specified multiple times, the first one wins break; } } // this is so that JFreeChart can work nicely even if we are launched as a daemon System.setProperty(""java.awt.headless"", ""true""); File me = whoAmI(extractedFilesFolder); System.out.println(""Running from: "" + me); System.setProperty(""executable-war"", me.getAbsolutePath()); // remember the location so that we can access it from within webapp // figure out the arguments trimOffOurOptions(arguments); arguments.add(0, ""--warfile="" + me.getAbsolutePath()); if (!hasOption(arguments, ""--webroot="")) { // defaults to ~/.jenkins/war since many users reported that cron job attempts to clean up // the contents in the temporary directory. final File jenkinsHome = getJenkinsHome(); final File webRoot = new File(jenkinsHome, ""war""); System.out.println(""webroot: "" + webRoot); arguments.add(""--webroot="" + webRoot); } // only do a cleanup if you set the extractedFilesFolder property. if (extractedFilesFolder != null) { deleteContentsFromFolder(extractedFilesFolder, ""winstone.*\\.jar""); } // put winstone jar in a file system so that we can load jars from there File tmpJar = extractFromJar(""winstone.jar"", ""winstone"", "".jar"", extractedFilesFolder); tmpJar.deleteOnExit(); // clean up any previously extracted copy, since // winstone doesn't do so and that causes problems when newer [MASK] of Jenkins // is deployed. File tempFile; try { tempFile = File.createTempFile(""dummy"", ""dummy""); } catch (IOException e) { throw new UncheckedIOException(e); } deleteWinstoneTempContents(new File(tempFile.getParent(), ""winstone/"" + me.getName())); if (!tempFile.delete()) { System.err.println(""Failed to delete temporary file: "" + tempFile); } // locate the Winstone launcher ClassLoader cl; try { cl = new URLClassLoader(new URL[] {tmpJar.toURI().toURL()}); } catch (MalformedURLException e) { throw new UncheckedIOException(e); } Class launcher; Method mainMethod; try { launcher = cl.loadClass(""winstone.Launcher""); mainMethod = launcher.getMethod(""main"", String[].class); } catch (ClassNotFoundException | NoSuchMethodException e) { throw new AssertionError(e); } // override the usage screen Field usage; try { usage = launcher.getField(""USAGE""); } catch (NoSuchFieldException e) { throw new AssertionError(e); } usage.set(null, ""Jenkins Automation Server Engine "" + getVersion("""") + ""\n"" + ""Usage: java -jar jenkins.war [--option=value] [--option=value]\n"" + ""\n"" + ""Options:\n"" + "" --webroot = folder where the WAR file is expanded into. Default is ${JENKINS_HOME}/war\n"" + "" --pluginroot = folder where the plugin archives are expanded into. Default is ${JENKINS_HOME}/plugins\n"" + "" (NOTE: this option does not change the directory where the plugin archives are stored)\n"" + "" --extractedFilesFolder = folder where extracted files are to be located. Default is the temp folder\n"" + "" "" + ENABLE_FUTURE_JAVA_CLI_SWITCH + "" = allows running with Java [MASK] s which are not fully supported\n"" + "" --paramsFromStdIn = Read parameters from standard input (stdin)\n"" + "" -- [MASK] = Print [MASK] to standard output (stdout) and exit\n"" + ""{OPTIONS}""); if (!DISABLE_CUSTOM_JSESSIONID_COOKIE_NAME) { /* Set an unique cookie name. As can be seen in discussions like http://stackoverflow.com/questions/1146112/jsessionid-collision-between-two-servers-on-same-ip-but-different-ports and http://stackoverflow.com/questions/1612177/are-http-cookies-port-specific, RFC 2965 says cookies from one port of one host may be sent to a different port of the same host. This means if someone runs multiple Jenkins on different ports of the same host, their sessions get mixed up. To fix the problem, use unique session cookie name. This change breaks the cluster mode of Winstone, as all nodes in the cluster must share the same session cookie name. Jenkins doesn't support clustered operation anyway, so we need to do this here, and not in Winstone. */ try { Field f = cl.loadClass(""winstone.WinstoneSession"").getField(""SESSION_COOKIE_NAME""); f.setAccessible(true); if (JSESSIONID_COOKIE_NAME != null) { // Use the user-defined cookie name f.set(null, JSESSIONID_COOKIE_NAME); } else { // Randomize session names by default to prevent collisions when running multiple Jenkins instances on the same host. f.set(null, ""JSESSIONID."" + UUID.randomUUID().toString().replace(""-"", """").substring(0, 8)); } } catch (ClassNotFoundException | NoSuchFieldException e) { throw new AssertionError(e); } } // run Thread.currentThread().setContextClassLoader(cl); try { mainMethod.invoke(null, new Object[] {arguments.toArray(new String[0])}); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof IOException) { throw new UncheckedIOException((IOException) t); } else if (t instanceof Exception) { throw new RuntimeException(t); } else if (t instanceof Error) { throw (Error) t; } else { throw new RuntimeException(e); } } } /** * Reads up to maxRead bytes from InputStream if available into a String * * @param in input stream to be read * @param maxToRead maximum number of bytes to read from the in * @return a String read from in */ private static String readStringNonBlocking(InputStream in, int maxToRead) { byte[] buffer; try { buffer = new byte[Math.min(in.available(), maxToRead)]; in.read(buffer); } catch (IOException e) { throw new UncheckedIOException(e); } return new String(buffer); } private static void trimOffOurOptions(List arguments) { arguments.removeIf(arg -> arg.startsWith(""--extractedFilesFolder"") || arg.startsWith(""--pluginroot"") || arg.startsWith(ENABLE_FUTURE_JAVA_CLI_SWITCH)); } /** * Figures out the [MASK] from the manifest. */ private static String getVersion(String fallback) { try { Enumeration manifests = Main.class.getClassLoader().getResources(""META-INF/MANIFEST.MF""); while (manifests.hasMoreElements()) { URL res = manifests.nextElement(); Manifest manifest = new Manifest(res.openStream()); String v = manifest.getMainAttributes().getValue(""Jenkins-Version""); if (v != null) { return v; } } } catch (IOException e) { throw new UncheckedIOException(e); } return fallback; } private static boolean hasOption(List args, String prefix) { for (String s : args) { if (s.startsWith(prefix)) { return true; } } return false; } /** * Figures out the URL of {@code jenkins.war}. */ @SuppressFBWarnings(value = {""PATH_TRAVERSAL_IN"", ""URLCONNECTION_SSRF_FD""}, justification = ""User provided values for running the program."") public static File whoAmI(File directory) { // JNLP returns the URL where the jar was originally placed (like http://jenkins-ci.org/...) // not the local cached file. So we need a rather round about approach to get to // the local file name. // There is no portable way to find where the locally cached copy // of jenkins.war/jar is; JDK 6 is too smart. (See JENKINS-2326.) try { URL classFile = Main.class.getClassLoader().getResource(""executable/Main.class""); JarFile jf = ((JarURLConnection) classFile.openConnection()).getJarFile(); return new File(jf.getName()); } catch (Exception x) { System.err.println(""ZipFile.name trick did not work, using fallback: "" + x); } File myself; try { myself = File.createTempFile(""jenkins"", "".jar"", directory); } catch (IOException e) { throw new UncheckedIOException(e); } myself.deleteOnExit(); try (InputStream is = Main.class.getProtectionDomain().getCodeSource().getLocation().openStream(); OutputStream os = new FileOutputStream(myself)) { copyStream(is, os); } catch (IOException e) { throw new UncheckedIOException(e); } return myself; } private static void copyStream(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[8192]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } /** * Extract a resource from jar, mark it for deletion upon exit, and return its location. */ @SuppressFBWarnings(value = ""PATH_TRAVERSAL_IN"", justification = ""User provided values for running the program."") private static File extractFromJar(String resource, String fileName, String suffix, File directory) { URL res = Main.class.getResource(resource); if (res == null) { throw new MissingResourceException(""Unable to find the resource: "" + resource, Main.class.getName(), resource); } // put this jar in a file system so that we can load jars from there File tmp; try { tmp = File.createTempFile(fileName, suffix, directory); } catch (IOException e) { String tmpdir = directory == null ? System.getProperty(""java.io.tmpdir"") : directory.getAbsolutePath(); throw new UncheckedIOException(""Jenkins failed to create a temporary file in "" + tmpdir + "": "" + e, e); } try (InputStream is = res.openStream(); OutputStream os = new FileOutputStream(tmp)) { copyStream(is, os); } catch (IOException e) { throw new UncheckedIOException(e); } tmp.deleteOnExit(); return tmp; } /** * Search contents to delete in a folder that match with some patterns. * * @param folder folder where the contents are. * @param patterns patterns that identifies the contents to search. */ private static void deleteContentsFromFolder(File folder, final String... patterns) { File[] files = folder.listFiles(); if (files != null) { for (File file : files) { for (String pattern : patterns) { if (file.getName().matches(pattern)) { deleteWinstoneTempContents(file); } } } } } private static void deleteWinstoneTempContents(File file) { if (!file.exists()) { return; } if (file.isDirectory()) { File[] files = file.listFiles(); if (files != null) { // be defensive for (File value : files) { deleteWinstoneTempContents(value); } } } if (!file.delete()) { System.err.println(""Failed to delete temporary Winstone file: "" + file); } } /** * Determines the home directory for Jenkins. * * People makes configuration mistakes, so we are trying to be nice * with those by doing {@link String#trim()}. */ @SuppressFBWarnings(value = ""PATH_TRAVERSAL_IN"", justification = ""User provided values for running the program."") private static File getJenkinsHome() { // check the system property for the home directory first for (String name : HOME_NAMES) { String sysProp = System.getProperty(name); if (sysProp != null) { return new File(sysProp.trim()); } } // look at the env var next for (String name : HOME_NAMES) { String env = System.getenv(name); if (env != null) { return new File(env.trim()); } } // otherwise pick a place by ourselves File legacyHome = new File(new File(System.getProperty(""user.home"")), "".hudson""); if (legacyHome.exists()) { return legacyHome; // before rename, this is where it was stored } return new File(new File(System.getProperty(""user.home"")), "".jenkins""); } private static final String[] HOME_NAMES = {""JENKINS_HOME"", ""HUDSON_HOME""}; } ","version " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO; import static com.google.android.exoplayer2.C.TRACK_TYPE_CAMERA_MOTION; import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; import static com.google.android.exoplayer2.Renderer.MSG_SET_PREFERRED_AUDIO_DEVICE; import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE; import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_EFFECTS; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT_RESOLUTION; import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.max; import static java.lang.Math.min; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioTrack; import android.media.MediaFormat; import android.media.metrics.LogSessionId; import android.os.Handler; import android.os.Looper; import android.util.Pair; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.Renderer.MessageType; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector; import com.google.android.exoplayer2.analytics.MediaMetricsListener; import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.Effect; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.Size; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeoutException; /** * The default implementation of {@link ExoPlayer}. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated /* package */ final class ExoPlayerImpl extends BasePlayer implements ExoPlayer, ExoPlayer.AudioComponent, ExoPlayer.VideoComponent, ExoPlayer.TextComponent, ExoPlayer.DeviceComponent { static { ExoPlayerLibraryInfo.registerModule(""goog.exo.exoplayer""); } private static final String TAG = ""ExoPlayerImpl""; /** * This empty track selector result can only be used for {@link PlaybackInfo#trackSelectorResult} * when the player does not have any track selection made (such as when player is reset, or when * player seeks to an unprepared period). It will not be used as result of any {@link * TrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId, Timeline)} * operation. */ /* package */ final TrackSelectorResult emptyTrackSelectorResult; /* package */ final Commands permanentAvailableCommands; private final ConditionVariable constructorFinished; private final Context applicationContext; private final Player wrappingPlayer; private final Renderer[] renderers; private final TrackSelector trackSelector; private final HandlerWrapper playbackInfoUpdateHandler; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal internalPlayer; private final ListenerSet listeners; private final CopyOnWriteArraySet audioOffloadListeners; private final Timeline.Period period; private final List mediaSourceHolderSnapshots; private final boolean useLazyPreparation; private final MediaSource.Factory mediaSourceFactory; private final AnalyticsCollector analyticsCollector; private final Looper applicationLooper; private final BandwidthMeter bandwidthMeter; private final long seekBackIncrementMs; private final long seekForwardIncrementMs; private final Clock clock; private final ComponentListener componentListener; private final FrameMetadataListener frameMetadataListener; private final AudioBecomingNoisyManager audioBecomingNoisyManager; private final AudioFocusManager audioFocusManager; @Nullable private final StreamVolumeManager streamVolumeManager; private final WakeLockManager wakeLockManager; private final WifiLockManager wifiLockManager; private final long detachSurfaceTimeoutMs; private @RepeatMode int repeatMode; private boolean shuffleModeEnabled; private int pendingOperationAcks; private @DiscontinuityReason int pendingDiscontinuityReason; private boolean pendingDiscontinuity; private @PlayWhenReadyChangeReason int pendingPlayWhenReadyChangeReason; private boolean foregroundMode; private SeekParameters seekParameters; private ShuffleOrder shuffleOrder; private boolean pauseAtEndOfMediaItems; private Commands availableCommands; private MediaMetadata mediaMetadata; private MediaMetadata playlistMetadata; @Nullable private Format videoFormat; @Nullable private Format audioFormat; @Nullable private AudioTrack keepSessionIdAudioTrack; @Nullable private Object videoOutput; @Nullable private Surface ownedSurface; @Nullable private SurfaceHolder surfaceHolder; @Nullable private SphericalGLSurfaceView sphericalGLSurfaceView; private boolean surfaceHolderSurfaceIsVideoOutput; @Nullable private TextureView textureView; private @C.VideoScalingMode int videoScalingMode; private @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy; private Size surfaceSize; @Nullable private DecoderCounters videoDecoderCounters; @Nullable private DecoderCounters audioDecoderCounters; private int audioSessionId; private AudioAttributes audioAttributes; private float volume; private boolean skipSilenceEnabled; private CueGroup currentCueGroup; @Nullable private VideoFrameMetadataListener videoFrameMetadataListener; @Nullable private CameraMotionListener cameraMotionListener; private boolean throwsWhenUsingWrongThread; private boolean hasNotifiedFullWrongThreadWarning; @Nullable private PriorityTaskManager priorityTaskManager; private boolean isPriorityTaskManagerRegistered; private boolean playerReleased; private DeviceInfo deviceInfo; private VideoSize videoSize; // MediaMetadata built from static (TrackGroup Format) and dynamic (onMetadata(Metadata)) metadata // sources. private MediaMetadata staticAndDynamicMediaMetadata; // Playback information when there is no pending seek/set source operation. private PlaybackInfo playbackInfo; // Playback information when there is a pending seek/set source operation. private int maskingWindowIndex; private int maskingPeriodIndex; private long maskingWindowPositionMs; @SuppressLint(""HandlerLeak"") @SuppressWarnings(""deprecation"") // Control flow for old volume commands public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer) { constructorFinished = new ConditionVariable(); try { Log.i( TAG, ""Init "" + Integer.toHexString(System.identityHashCode(this)) + "" ["" + ExoPlayerLibraryInfo.VERSION_SLASHY + ""] ["" + Util.DEVICE_DEBUG_INFO + ""]""); applicationContext = builder.context.getApplicationContext(); analyticsCollector = builder.analyticsCollectorFunction.apply(builder.clock); priorityTaskManager = builder.priorityTaskManager; audioAttributes = builder.audioAttributes; videoScalingMode = builder.videoScalingMode; videoChangeFrameRateStrategy = builder.videoChangeFrameRateStrategy; skipSilenceEnabled = builder.skipSilenceEnabled; detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; componentListener = new ComponentListener(); frameMetadataListener = new FrameMetadataListener(); Handler eventHandler = new Handler(builder.looper); renderers = builder .renderersFactorySupplier .get() .createRenderers( eventHandler, componentListener, componentListener, componentListener, componentListener); checkState(renderers.length > 0); this.trackSelector = builder.trackSelectorSupplier.get(); this.mediaSourceFactory = builder.mediaSourceFactorySupplier.get(); this.bandwidthMeter = builder.bandwidthMeterSupplier.get(); this.useLazyPreparation = builder.useLazyPreparation; this.seekParameters = builder.seekParameters; this.seekBackIncrementMs = builder.seekBackIncrementMs; this.seekForwardIncrementMs = builder.seekForwardIncrementMs; this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems; this.applicationLooper = builder.looper; this.clock = builder.clock; this.wrappingPlayer = wrappingPlayer == null ? this : wrappingPlayer; listeners = new ListenerSet<>( applicationLooper, clock, (listener, flags) -> listener.onEvents(this.wrappingPlayer, new Events(flags))); audioOffloadListeners = new CopyOnWriteArraySet<>(); mediaSourceHolderSnapshots = new ArrayList<>(); shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); emptyTrackSelectorResult = new TrackSelectorResult( new RendererConfiguration[renderers.length], new ExoTrackSelection[renderers.length], Tracks.EMPTY, /* info= */ null); period = new Timeline.Period(); permanentAvailableCommands = new Commands.Builder() .addAll( COMMAND_PLAY_PAUSE, COMMAND_PREPARE, COMMAND_STOP, COMMAND_SET_SPEED_AND_PITCH, COMMAND_SET_SHUFFLE_MODE, COMMAND_SET_REPEAT_MODE, COMMAND_GET_CURRENT_MEDIA_ITEM, COMMAND_GET_TIMELINE, COMMAND_GET_METADATA, COMMAND_SET_PLAYLIST_METADATA, COMMAND_SET_MEDIA_ITEM, COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_GET_TRACKS, COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_VOLUME, COMMAND_SET_VOLUME, COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, COMMAND_RELEASE) .addIf( COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) .addIf(COMMAND_GET_DEVICE_VOLUME, builder.deviceVolumeControlEnabled) .addIf(COMMAND_SET_DEVICE_VOLUME, builder.deviceVolumeControlEnabled) .addIf(COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, builder.deviceVolumeControlEnabled) .addIf(COMMAND_ADJUST_DEVICE_VOLUME, builder.deviceVolumeControlEnabled) .addIf(COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, builder.deviceVolumeControlEnabled) .build(); availableCommands = new Commands.Builder() .addAll(permanentAvailableCommands) .add(COMMAND_SEEK_TO_DEFAULT_POSITION) .add(COMMAND_SEEK_TO_MEDIA_ITEM) .build(); playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null); playbackInfoUpdateListener = playbackInfoUpdate -> playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); analyticsCollector.setPlayer(this.wrappingPlayer, applicationLooper); PlayerId playerId = Util.SDK_INT < 31 ? new PlayerId() : Api31.registerMediaMetricsListener( applicationContext, /* player= */ this, builder.usePlatformDiagnostics); internalPlayer = new ExoPlayerImplInternal( renderers, trackSelector, emptyTrackSelectorResult, builder.loadControlSupplier.get(), bandwidthMeter, repeatMode, shuffleModeEnabled, analyticsCollector, seekParameters, builder.livePlaybackSpeedControl, builder.releaseTimeoutMs, pauseAtEndOfMediaItems, applicationLooper, clock, playbackInfoUpdateListener, playerId, builder.playbackLooper); volume = 1; repeatMode = Player.REPEAT_MODE_OFF; mediaMetadata = MediaMetadata.EMPTY; playlistMetadata = MediaMetadata.EMPTY; staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; maskingWindowIndex = C.INDEX_UNSET; if (Util.SDK_INT < 21) { audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); } else { audioSessionId = Util.generateAudioSessionIdV21(applicationContext); } currentCueGroup = CueGroup.EMPTY_TIME_ZERO; throwsWhenUsingWrongThread = true; addListener(analyticsCollector); bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector); addAudioOffloadListener(componentListener); if (builder.foregroundModeTimeoutMs > 0) { internalPlayer.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs); } audioBecomingNoisyManager = new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener); audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy); audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener); audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null); if (builder.deviceVolumeControlEnabled) { streamVolumeManager = new StreamVolumeManager(builder.context, eventHandler, componentListener); streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); } else { streamVolumeManager = null; } wakeLockManager = new WakeLockManager(builder.context); wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE); wifiLockManager = new WifiLockManager(builder.context); wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); deviceInfo = createDeviceInfo(streamVolumeManager); videoSize = VideoSize.UNKNOWN; surfaceSize = Size.UNKNOWN; trackSelector.setAudioAttributes(audioAttributes); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener); sendRendererMessage( TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener); } finally { constructorFinished.open(); } } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public AudioComponent getAudioComponent() { verifyApplicationThread(); return this; } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public VideoComponent getVideoComponent() { verifyApplicationThread(); return this; } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public TextComponent getTextComponent() { verifyApplicationThread(); return this; } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public DeviceComponent getDeviceComponent() { verifyApplicationThread(); return this; } @Override public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { verifyApplicationThread(); internalPlayer.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); for (AudioOffloadListener listener : audioOffloadListeners) { listener.onExperimentalOffloadSchedulingEnabledChanged(offloadSchedulingEnabled); } } @Override public boolean experimentalIsSleepingForOffload() { verifyApplicationThread(); return playbackInfo.sleepingForOffload; } @Override public Looper getPlaybackLooper() { // Don't verify application thread. We allow calls to this method from any thread. return internalPlayer.getPlaybackLooper(); } @Override public Looper getApplicationLooper() { // Don't verify application thread. We allow calls to this method from any thread. return applicationLooper; } @Override public Clock getClock() { // Don't verify application thread. We allow calls to this method from any thread. return clock; } @Override public void addAudioOffloadListener(AudioOffloadListener listener) { // Don't verify application thread. We allow calls to this method from any thread. audioOffloadListeners.add(listener); } @Override public void removeAudioOffloadListener(AudioOffloadListener listener) { verifyApplicationThread(); audioOffloadListeners.remove(listener); } @Override public Commands getAvailableCommands() { verifyApplicationThread(); return availableCommands; } @Override public @State int getPlaybackState() { verifyApplicationThread(); return playbackInfo.playbackState; } @Override public @PlaybackSuppressionReason int getPlaybackSuppressionReason() { verifyApplicationThread(); return playbackInfo.playbackSuppressionReason; } @Override @Nullable public ExoPlaybackException getPlayerError() { verifyApplicationThread(); return playbackInfo.playbackError; } @Override public void prepare() { verifyApplicationThread(); boolean playWhenReady = getPlayWhenReady(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); if (playbackInfo.playbackState != Player.STATE_IDLE) { return; } PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackError(null); playbackInfo = playbackInfo.copyWithPlaybackState( playbackInfo.timeline.isEmpty() ? STATE_ENDED : STATE_BUFFERING); // Trigger internal prepare first before updating the playback info and notifying external // listeners to ensure that new operations issued in the listener notifications reach the // player after this prepare. The internal player can't change the playback info immediately // because it uses a callback. pendingOperationAcks++; internalPlayer.prepare(); updatePlaybackInfo( playbackInfo, /* ignored */ TIMELINE_CHANGE_REASON_SOURCE_UPDATE, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override @Deprecated public void prepare(MediaSource mediaSource) { verifyApplicationThread(); setMediaSource(mediaSource); prepare(); } @Override @Deprecated public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { verifyApplicationThread(); setMediaSource(mediaSource, resetPosition); prepare(); } @Override public void setMediaItems(List mediaItems, boolean resetPosition) { verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), resetPosition); } @Override public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), startIndex, startPositionMs); } @Override public void setMediaSource(MediaSource mediaSource) { verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource)); } @Override public void setMediaSource(MediaSource mediaSource, long startPositionMs) { verifyApplicationThread(); setMediaSources( Collections.singletonList(mediaSource), /* startWindowIndex= */ 0, startPositionMs); } @Override public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource), resetPosition); } @Override public void setMediaSources(List mediaSources) { verifyApplicationThread(); setMediaSources(mediaSources, /* resetPosition= */ true); } @Override public void setMediaSources(List mediaSources, boolean resetPosition) { verifyApplicationThread(); setMediaSourcesInternal( mediaSources, /* startWindowIndex= */ C.INDEX_UNSET, /* startPositionMs= */ C.TIME_UNSET, /* resetToDefaultPosition= */ resetPosition); } @Override public void setMediaSources( List mediaSources, int startWindowIndex, long startPositionMs) { verifyApplicationThread(); setMediaSourcesInternal( mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false); } @Override public void addMediaItems(int index, List mediaItems) { verifyApplicationThread(); addMediaSources(index, createMediaSources(mediaItems)); } @Override public void addMediaSource(MediaSource mediaSource) { verifyApplicationThread(); addMediaSources(Collections.singletonList(mediaSource)); } @Override public void addMediaSource(int index, MediaSource mediaSource) { verifyApplicationThread(); addMediaSources(index, Collections.singletonList(mediaSource)); } @Override public void addMediaSources(List mediaSources) { verifyApplicationThread(); addMediaSources(/* index= */ mediaSourceHolderSnapshots.size(), mediaSources); } @Override public void addMediaSources(int index, List mediaSources) { verifyApplicationThread(); checkArgument(index >= 0); index = min(index, mediaSourceHolderSnapshots.size()); if (mediaSourceHolderSnapshots.isEmpty()) { // Handle initial items in a playlist as a set operation to ensure state changes and initial // position are updated correctly. setMediaSources(mediaSources, /* resetPosition= */ maskingWindowIndex == C.INDEX_UNSET); return; } PlaybackInfo newPlaybackInfo = addMediaSourcesInternal(playbackInfo, index, mediaSources); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void removeMediaItems(int fromIndex, int toIndex) { verifyApplicationThread(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex); int playlistSize = mediaSourceHolderSnapshots.size(); toIndex = min(toIndex, playlistSize); if (fromIndex >= playlistSize || fromIndex == toIndex) { // Do nothing. return; } PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(playbackInfo, fromIndex, toIndex); boolean positionDiscontinuity = !newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, positionDiscontinuity, DISCONTINUITY_REASON_REMOVE, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { verifyApplicationThread(); checkArgument(fromIndex >= 0 && fromIndex <= toIndex && newFromIndex >= 0); int playlistSize = mediaSourceHolderSnapshots.size(); toIndex = min(toIndex, playlistSize); newFromIndex = min(newFromIndex, playlistSize - (toIndex - fromIndex)); if (fromIndex >= playlistSize || fromIndex == toIndex || fromIndex == newFromIndex) { // Do nothing. return; } Timeline oldTimeline = getCurrentTimeline(); pendingOperationAcks++; Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex); Timeline newTimeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, newTimeline, getPeriodPositionUsAfterTimelineChanged( oldTimeline, newTimeline, getCurrentWindowIndexInternal(playbackInfo), getContentPositionInternal(playbackInfo))); internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void replaceMediaItems(int fromIndex, int toIndex, List mediaItems) { verifyApplicationThread(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex); int playlistSize = mediaSourceHolderSnapshots.size(); if (fromIndex > playlistSize) { // Do nothing. return; } toIndex = min(toIndex, playlistSize); List mediaSources = createMediaSources(mediaItems); if (mediaSourceHolderSnapshots.isEmpty()) { // Handle initial items in a playlist as a set operation to ensure state changes and initial // position are updated correctly. setMediaSources(mediaSources, /* resetPosition= */ maskingWindowIndex == C.INDEX_UNSET); return; } PlaybackInfo newPlaybackInfo = addMediaSourcesInternal(playbackInfo, toIndex, mediaSources); newPlaybackInfo = removeMediaItemsInternal(newPlaybackInfo, fromIndex, toIndex); boolean positionDiscontinuity = !newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, positionDiscontinuity, DISCONTINUITY_REASON_REMOVE, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { verifyApplicationThread(); checkArgument(shuffleOrder.getLength() == mediaSourceHolderSnapshots.size()); this.shuffleOrder = shuffleOrder; Timeline timeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, timeline, maskWindowPositionMsOrGetPeriodPositionUs( timeline, getCurrentMediaItemIndex(), getCurrentPosition())); pendingOperationAcks++; internalPlayer.setShuffleOrder(shuffleOrder); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { verifyApplicationThread(); if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) { return; } this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; internalPlayer.setPauseAtEndOfWindow(pauseAtEndOfMediaItems); } @Override public boolean getPauseAtEndOfMediaItems() { verifyApplicationThread(); return pauseAtEndOfMediaItems; } @Override public void setPlayWhenReady(boolean playWhenReady) { verifyApplicationThread(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); } @Override public boolean getPlayWhenReady() { verifyApplicationThread(); return playbackInfo.playWhenReady; } @Override public void setRepeatMode(@RepeatMode int repeatMode) { verifyApplicationThread(); if (this.repeatMode != repeatMode) { this.repeatMode = repeatMode; internalPlayer.setRepeatMode(repeatMode); listeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode)); updateAvailableCommands(); listeners.flushEvents(); } } @Override public @RepeatMode int getRepeatMode() { verifyApplicationThread(); return repeatMode; } @Override public void setShuffleModeEnabled(boolean shuffleModeEnabled) { verifyApplicationThread(); if (this.shuffleModeEnabled != shuffleModeEnabled) { this.shuffleModeEnabled = shuffleModeEnabled; internalPlayer.setShuffleModeEnabled(shuffleModeEnabled); listeners.queueEvent( Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled)); updateAvailableCommands(); listeners.flushEvents(); } } @Override public boolean getShuffleModeEnabled() { verifyApplicationThread(); return shuffleModeEnabled; } @Override public boolean isLoading() { verifyApplicationThread(); return playbackInfo.isLoading; } @Override public void seekTo( int mediaItemIndex, long positionMs, @Player.Command int seekCommand, boolean isRepeatingCurrentItem) { verifyApplicationThread(); checkArgument(mediaItemIndex >= 0); analyticsCollector.notifySeekStarted(); Timeline timeline = playbackInfo.timeline; if (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount()) { return; } pendingOperationAcks++; if (isPlayingAd()) { // TODO: Investigate adding support for seeking during ads. This is complicated to do in // general because the midroll ad preceding the seek destination must be played before the // content position can be played, if a different ad is playing at the moment. Log.w(TAG, ""seekTo ignored because an ad is playing""); ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate = new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo); playbackInfoUpdate.incrementPendingOperationAcks(1); playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate); return; } PlaybackInfo newPlaybackInfo = playbackInfo; if (playbackInfo.playbackState == Player.STATE_READY || (playbackInfo.playbackState == Player.STATE_ENDED && !timeline.isEmpty())) { newPlaybackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_BUFFERING); } int oldMaskingMediaItemIndex = getCurrentMediaItemIndex(); newPlaybackInfo = maskTimelineAndPosition( newPlaybackInfo, timeline, maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs)); internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs)); updatePlaybackInfo( newPlaybackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ true, /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), oldMaskingMediaItemIndex, isRepeatingCurrentItem); } @Override public long getSeekBackIncrement() { verifyApplicationThread(); return seekBackIncrementMs; } @Override public long getSeekForwardIncrement() { verifyApplicationThread(); return seekForwardIncrementMs; } @Override public long getMaxSeekToPreviousPosition() { verifyApplicationThread(); return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; } @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { verifyApplicationThread(); if (playbackParameters == null) { playbackParameters = PlaybackParameters.DEFAULT; } if (playbackInfo.playbackParameters.equals(playbackParameters)) { return; } PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters); pendingOperationAcks++; internalPlayer.setPlaybackParameters(playbackParameters); updatePlaybackInfo( newPlaybackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public PlaybackParameters getPlaybackParameters() { verifyApplicationThread(); return playbackInfo.playbackParameters; } @Override public void setSeekParameters(@Nullable SeekParameters seekParameters) { verifyApplicationThread(); if (seekParameters == null) { seekParameters = SeekParameters.DEFAULT; } if (!this.seekParameters.equals(seekParameters)) { this.seekParameters = seekParameters; internalPlayer.setSeekParameters(seekParameters); } } @Override public SeekParameters getSeekParameters() { verifyApplicationThread(); return seekParameters; } @Override public void setForegroundMode(boolean foregroundMode) { verifyApplicationThread(); if (this.foregroundMode != foregroundMode) { this.foregroundMode = foregroundMode; if (!internalPlayer.setForegroundMode(foregroundMode)) { // One of the renderers timed out releasing its resources. stopInternal( ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_SET_FOREGROUND_MODE), PlaybackException.ERROR_CODE_TIMEOUT)); } } } @Override public void stop() { verifyApplicationThread(); audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); stopInternal(/* error= */ null); currentCueGroup = new CueGroup(ImmutableList.of(), playbackInfo.positionUs); } @Override public void release() { Log.i( TAG, ""Release "" + Integer.toHexString(System.identityHashCode(this)) + "" ["" + ExoPlayerLibraryInfo.VERSION_SLASHY + ""] ["" + Util.DEVICE_DEBUG_INFO + ""] ["" + ExoPlayerLibraryInfo.registeredModules() + ""]""); verifyApplicationThread(); if (Util.SDK_INT < 21 && keepSessionIdAudioTrack != null) { keepSessionIdAudioTrack.release(); keepSessionIdAudioTrack = null; } audioBecomingNoisyManager.setEnabled(false); if (streamVolumeManager != null) { streamVolumeManager.release(); } wakeLockManager.setStayAwake(false); wifiLockManager.setStayAwake(false); audioFocusManager.release(); if (!internalPlayer.release()) { // One of the renderers timed out releasing its resources. listeners.sendEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError( ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_RELEASE), PlaybackException.ERROR_CODE_TIMEOUT))); } listeners.release(); playbackInfoUpdateHandler.removeCallbacksAndMessages(null); bandwidthMeter.removeEventListener(analyticsCollector); if (playbackInfo.sleepingForOffload) { playbackInfo = playbackInfo.copyWithEstimatedPosition(); } playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; playbackInfo.totalBufferedDurationUs = 0; analyticsCollector.release(); trackSelector.release(); removeSurfaceCallbacks(); if (ownedSurface != null) { ownedSurface.release(); ownedSurface = null; } if (isPriorityTaskManagerRegistered) { checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = false; } currentCueGroup = CueGroup.EMPTY_TIME_ZERO; playerReleased = true; } @Override public PlayerMessage createMessage(Target target) { verifyApplicationThread(); return createMessageInternal(target); } @Override public int getCurrentPeriodIndex() { verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingPeriodIndex; } else { return playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid); } } @Override public int getCurrentMediaItemIndex() { verifyApplicationThread(); int currentWindowIndex = getCurrentWindowIndexInternal(playbackInfo); return currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex; } @Override public long getDuration() { verifyApplicationThread(); if (isPlayingAd()) { MediaPeriodId periodId = playbackInfo.periodId; playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup); return Util.usToMs(adDurationUs); } return getContentDuration(); } @Override public long getCurrentPosition() { verifyApplicationThread(); return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); } @Override public long getBufferedPosition() { verifyApplicationThread(); if (isPlayingAd()) { return playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId) ? Util.usToMs(playbackInfo.bufferedPositionUs) : getDuration(); } return getContentBufferedPosition(); } @Override public long getTotalBufferedDuration() { verifyApplicationThread(); return Util.usToMs(playbackInfo.totalBufferedDurationUs); } @Override public boolean isPlayingAd() { verifyApplicationThread(); return playbackInfo.periodId.isAd(); } @Override public int getCurrentAdGroupIndex() { verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; } @Override public int getCurrentAdIndexInAdGroup() { verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; } @Override public long getContentPosition() { verifyApplicationThread(); return getContentPositionInternal(playbackInfo); } @Override public long getContentBufferedPosition() { verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingWindowPositionMs; } if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber != playbackInfo.periodId.windowSequenceNumber) { return playbackInfo.timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } long contentBufferedPositionUs = playbackInfo.bufferedPositionUs; if (playbackInfo.loadingMediaPeriodId.isAd()) { Timeline.Period loadingPeriod = playbackInfo.timeline.getPeriodByUid(playbackInfo.loadingMediaPeriodId.periodUid, period); contentBufferedPositionUs = loadingPeriod.getAdGroupTimeUs(playbackInfo.loadingMediaPeriodId.adGroupIndex); if (contentBufferedPositionUs == C.TIME_END_OF_SOURCE) { contentBufferedPositionUs = loadingPeriod.durationUs; } } return Util.usToMs( periodPositionUsToWindowPositionUs( playbackInfo.timeline, playbackInfo.loadingMediaPeriodId, contentBufferedPositionUs)); } @Override public int getRendererCount() { verifyApplicationThread(); return renderers.length; } @Override public @C.TrackType int getRendererType(int index) { verifyApplicationThread(); return renderers[index].getTrackType(); } @Override public Renderer getRenderer(int index) { verifyApplicationThread(); return renderers[index]; } @Override public TrackSelector getTrackSelector() { verifyApplicationThread(); return trackSelector; } @Override public TrackGroupArray getCurrentTrackGroups() { verifyApplicationThread(); return playbackInfo.trackGroups; } @Override public TrackSelectionArray getCurrentTrackSelections() { verifyApplicationThread(); return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections); } @Override public Tracks getCurrentTracks() { verifyApplicationThread(); return playbackInfo.trackSelectorResult.tracks; } @Override public TrackSelectionParameters getTrackSelectionParameters() { verifyApplicationThread(); return trackSelector.getParameters(); } @Override public void setTrackSelectionParameters(TrackSelectionParameters parameters) { verifyApplicationThread(); if (!trackSelector.isSetParametersSupported() || parameters.equals(trackSelector.getParameters())) { return; } trackSelector.setParameters(parameters); listeners.sendEvent( EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, listener -> listener.onTrackSelectionParametersChanged(parameters)); } @Override public MediaMetadata getMediaMetadata() { verifyApplicationThread(); return mediaMetadata; } @Override public MediaMetadata getPlaylistMetadata() { verifyApplicationThread(); return playlistMetadata; } @Override public void setPlaylistMetadata(MediaMetadata playlistMetadata) { verifyApplicationThread(); checkNotNull(playlistMetadata); if (playlistMetadata.equals(this.playlistMetadata)) { return; } this.playlistMetadata = playlistMetadata; listeners.sendEvent( EVENT_PLAYLIST_METADATA_CHANGED, listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata)); } @Override public Timeline getCurrentTimeline() { verifyApplicationThread(); return playbackInfo.timeline; } @Override public void setVideoEffects(List videoEffects) { verifyApplicationThread(); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_VIDEO_EFFECTS, videoEffects); } @Override public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { verifyApplicationThread(); this.videoScalingMode = videoScalingMode; sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); } @Override public @C.VideoScalingMode int getVideoScalingMode() { verifyApplicationThread(); return videoScalingMode; } @Override public void setVideoChangeFrameRateStrategy( @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { verifyApplicationThread(); if (this.videoChangeFrameRateStrategy == videoChangeFrameRateStrategy) { return; } this.videoChangeFrameRateStrategy = videoChangeFrameRateStrategy; sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); } @Override public @C.VideoChangeFrameRateStrategy int getVideoChangeFrameRateStrategy() { verifyApplicationThread(); return videoChangeFrameRateStrategy; } @Override public VideoSize getVideoSize() { verifyApplicationThread(); return videoSize; } @Override public Size getSurfaceSize() { verifyApplicationThread(); return surfaceSize; } @Override public void clearVideoSurface() { verifyApplicationThread(); removeSurfaceCallbacks(); setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } @Override public void clearVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); if (surface != null && surface == videoOutput) { clearVideoSurface(); } } @Override public void setVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); removeSurfaceCallbacks(); setVideoOutputInternal(surface); int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET; maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder == null) { clearVideoSurface(); } else { removeSurfaceCallbacks(); this.surfaceHolderSurfaceIsVideoOutput = true; this.surfaceHolder = surfaceHolder; surfaceHolder.addCallback(componentListener); Surface surface = surfaceHolder.getSurface(); if (surface != null && surface.isValid()) { setVideoOutputInternal(surface); Rect surfaceSize = surfaceHolder.getSurfaceFrame(); maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height()); } else { setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } } } @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { clearVideoSurface(); } } @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); if (surfaceView instanceof VideoDecoderOutputBufferRenderer) { removeSurfaceCallbacks(); setVideoOutputInternal(surfaceView); setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); } else if (surfaceView instanceof SphericalGLSurfaceView) { removeSurfaceCallbacks(); sphericalGLSurfaceView = (SphericalGLSurfaceView) surfaceView; createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) .setPayload(sphericalGLSurfaceView) .send(); sphericalGLSurfaceView.addVideoSurfaceListener(componentListener); setVideoOutputInternal(sphericalGLSurfaceView.getVideoSurface()); setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); } else { setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } } @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } @Override public void setVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView == null) { clearVideoSurface(); } else { removeSurfaceCallbacks(); this.textureView = textureView; if (textureView.getSurfaceTextureListener() != null) { Log.w(TAG, ""Replacing existing SurfaceTextureListener.""); } textureView.setSurfaceTextureListener(componentListener); @Nullable SurfaceTexture surfaceTexture = textureView.isAvailable() ? textureView.getSurfaceTexture() : null; if (surfaceTexture == null) { setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } else { setSurfaceTextureInternal(surfaceTexture); maybeNotifySurfaceSizeChanged(textureView.getWidth(), textureView.getHeight()); } } } @Override public void clearVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView != null && textureView == this.textureView) { clearVideoSurface(); } } @Override public void setAudioAttributes(AudioAttributes newAudioAttributes, boolean handleAudioFocus) { verifyApplicationThread(); if (playerReleased) { return; } if (!Util.areEqual(this.audioAttributes, newAudioAttributes)) { this.audioAttributes = newAudioAttributes; sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, newAudioAttributes); if (streamVolumeManager != null) { streamVolumeManager.setStreamType( Util.getStreamTypeForAudioUsage(newAudioAttributes.usage)); } // Queue event only and flush after updating playWhenReady in case both events are triggered. listeners.queueEvent( EVENT_AUDIO_ATTRIBUTES_CHANGED, listener -> listener.onAudioAttributesChanged(newAudioAttributes)); } audioFocusManager.setAudioAttributes(handleAudioFocus ? newAudioAttributes : null); trackSelector.setAudioAttributes(newAudioAttributes); boolean playWhenReady = getPlayWhenReady(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); listeners.flushEvents(); } @Override public AudioAttributes getAudioAttributes() { verifyApplicationThread(); return audioAttributes; } @Override public void setAudioSessionId(int audioSessionId) { verifyApplicationThread(); if (this.audioSessionId == audioSessionId) { return; } if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { if (Util.SDK_INT < 21) { audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); } else { audioSessionId = Util.generateAudioSessionIdV21(applicationContext); } } else if (Util.SDK_INT < 21) { // We need to re-initialize keepSessionIdAudioTrack to make sure the session is kept alive for // as long as the player is using it. initializeKeepSessionIdAudioTrack(audioSessionId); } this.audioSessionId = audioSessionId; sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); int finalAudioSessionId = audioSessionId; listeners.sendEvent( EVENT_AUDIO_SESSION_ID, listener -> listener.onAudioSessionIdChanged(finalAudioSessionId)); } @Override public int getAudioSessionId() { verifyApplicationThread(); return audioSessionId; } @Override public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { verifyApplicationThread(); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); } @Override public void clearAuxEffectInfo() { verifyApplicationThread(); setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); } @RequiresApi(23) @Override public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { verifyApplicationThread(); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_PREFERRED_AUDIO_DEVICE, audioDeviceInfo); } @Override public void setVolume(float volume) { verifyApplicationThread(); volume = Util.constrainValue(volume, /* min= */ 0, /* max= */ 1); if (this.volume == volume) { return; } this.volume = volume; sendVolumeToRenderers(); float finalVolume = volume; listeners.sendEvent(EVENT_VOLUME_CHANGED, listener -> listener.onVolumeChanged(finalVolume)); } @Override public float getVolume() { verifyApplicationThread(); return volume; } @Override public boolean getSkipSilenceEnabled() { verifyApplicationThread(); return skipSilenceEnabled; } @Override public void setSkipSilenceEnabled(boolean newSkipSilenceEnabled) { verifyApplicationThread(); if (skipSilenceEnabled == newSkipSilenceEnabled) { return; } skipSilenceEnabled = newSkipSilenceEnabled; sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, newSkipSilenceEnabled); listeners.sendEvent( EVENT_SKIP_SILENCE_ENABLED_CHANGED, listener -> listener.onSkipSilenceEnabledChanged(newSkipSilenceEnabled)); } @Override public AnalyticsCollector getAnalyticsCollector() { verifyApplicationThread(); return analyticsCollector; } @Override public void addAnalyticsListener(AnalyticsListener listener) { // Don't verify application thread. We allow calls to this method from any thread. analyticsCollector.addListener(checkNotNull(listener)); } @Override public void removeAnalyticsListener(AnalyticsListener listener) { verifyApplicationThread(); analyticsCollector.removeListener(checkNotNull(listener)); } @Override public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { verifyApplicationThread(); if (playerReleased) { return; } audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy); } @Override public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { verifyApplicationThread(); if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) { return; } if (isPriorityTaskManagerRegistered) { checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK); } if (priorityTaskManager != null && isLoading()) { priorityTaskManager.add(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = true; } else { isPriorityTaskManagerRegistered = false; } this.priorityTaskManager = priorityTaskManager; } @Override @Nullable public Format getVideoFormat() { verifyApplicationThread(); return videoFormat; } @Override @Nullable public Format getAudioFormat() { verifyApplicationThread(); return audioFormat; } @Override @Nullable public DecoderCounters getVideoDecoderCounters() { verifyApplicationThread(); return videoDecoderCounters; } @Override @Nullable public DecoderCounters getAudioDecoderCounters() { verifyApplicationThread(); return audioDecoderCounters; } @Override public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); videoFrameMetadataListener = listener; createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) .setPayload(listener) .send(); } @Override public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); if (videoFrameMetadataListener != listener) { return; } createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) .setPayload(null) .send(); } @Override public void setCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); cameraMotionListener = listener; createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) .setPayload(listener) .send(); } @Override public void clearCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); if (cameraMotionListener != listener) { return; } createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) .setPayload(null) .send(); } @Override public CueGroup getCurrentCues() { verifyApplicationThread(); return currentCueGroup; } @Override public void addListener(Listener listener) { // Don't verify application thread. We allow calls to this method from any thread. listeners.add(checkNotNull(listener)); } @Override public void removeListener(Listener listener) { verifyApplicationThread(); listeners.remove(checkNotNull(listener)); } @Override public void setWakeMode(@C.WakeMode int wakeMode) { verifyApplicationThread(); switch (wakeMode) { case C.WAKE_MODE_NONE: wakeLockManager.setEnabled(false); wifiLockManager.setEnabled(false); break; case C.WAKE_MODE_LOCAL: wakeLockManager.setEnabled(true); wifiLockManager.setEnabled(false); break; case C.WAKE_MODE_NETWORK: wakeLockManager.setEnabled(true); wifiLockManager.setEnabled(true); break; default: break; } } @Override public DeviceInfo getDeviceInfo() { verifyApplicationThread(); return deviceInfo; } @Override public int getDeviceVolume() { verifyApplicationThread(); if (streamVolumeManager != null) { return streamVolumeManager.getVolume(); } else { return 0; } } @Override public boolean isDeviceMuted() { verifyApplicationThread(); if (streamVolumeManager != null) { return streamVolumeManager.isMuted(); } else { return false; } } /** * @deprecated Use {@link #setDeviceVolume(int, int)} instead. */ @Deprecated @Override public void setDeviceVolume(int volume) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setVolume(volume, C.VOLUME_FLAG_SHOW_UI); } } @Override public void setDeviceVolume(int volume, @C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setVolume(volume, flags); } } /** * @deprecated Use {@link #increaseDeviceVolume(int)} instead. */ @Deprecated @Override public void increaseDeviceVolume() { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.increaseVolume(C.VOLUME_FLAG_SHOW_UI); } } @Override public void increaseDeviceVolume(@C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.increaseVolume(flags); } } /** * @deprecated Use {@link #decreaseDeviceVolume(int)} instead. */ @Deprecated @Override public void decreaseDeviceVolume() { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.decreaseVolume(C.VOLUME_FLAG_SHOW_UI); } } @Override public void decreaseDeviceVolume(@C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.decreaseVolume(flags); } } /** * @deprecated Use {@link #setDeviceMuted(boolean, int)} instead. */ @Deprecated @Override public void setDeviceMuted(boolean muted) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setMuted(muted, C.VOLUME_FLAG_SHOW_UI); } } @Override public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setMuted(muted, flags); } } @Override public boolean isTunnelingEnabled() { verifyApplicationThread(); for (@Nullable RendererConfiguration config : playbackInfo.trackSelectorResult.rendererConfigurations) { if (config != null && config.tunneling) { return true; } } return false; } @SuppressWarnings(""deprecation"") // Calling deprecated methods. /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); if (analyticsCollector instanceof DefaultAnalyticsCollector) { ((DefaultAnalyticsCollector) analyticsCollector) .setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); } } /** * Stops the player. * * @param error An optional {@link ExoPlaybackException} to set. */ private void stopInternal(@Nullable ExoPlaybackException error) { PlaybackInfo playbackInfo = this.playbackInfo.copyWithLoadingMediaPeriodId(this.playbackInfo.periodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; playbackInfo.totalBufferedDurationUs = 0; playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); if (error != null) { playbackInfo = playbackInfo.copyWithPlaybackError(error); } pendingOperationAcks++; internalPlayer.stop(); updatePlaybackInfo( playbackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } private int getCurrentWindowIndexInternal(PlaybackInfo playbackInfo) { if (playbackInfo.timeline.isEmpty()) { return maskingWindowIndex; } return playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period) .windowIndex; } private long getContentPositionInternal(PlaybackInfo playbackInfo) { if (playbackInfo.periodId.isAd()) { playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET ? playbackInfo .timeline .getWindow(getCurrentWindowIndexInternal(playbackInfo), window) .getDefaultPositionMs() : period.getPositionInWindowMs() + Util.usToMs(playbackInfo.requestedContentPositionUs); } return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); } private long getCurrentPositionUsInternal(PlaybackInfo playbackInfo) { if (playbackInfo.timeline.isEmpty()) { return Util.msToUs(maskingWindowPositionMs); } long positionUs = playbackInfo.sleepingForOffload ? playbackInfo.getEstimatedPositionUs() : playbackInfo.positionUs; if (playbackInfo.periodId.isAd()) { return positionUs; } return periodPositionUsToWindowPositionUs( playbackInfo.timeline, playbackInfo.periodId, positionUs); } private List createMediaSources(List mediaItems) { List mediaSources = new ArrayList<>(); for (int i = 0; i < mediaItems.size(); i++) { mediaSources.add(mediaSourceFactory.createMediaSource(mediaItems.get(i))); } return mediaSources; } private void handlePlaybackInfo(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate) { pendingOperationAcks -= playbackInfoUpdate.operationAcks; if (playbackInfoUpdate.positionDiscontinuity) { pendingDiscontinuityReason = playbackInfoUpdate.discontinuityReason; pendingDiscontinuity = true; } if (playbackInfoUpdate.hasPlayWhenReadyChangeReason) { pendingPlayWhenReadyChangeReason = playbackInfoUpdate.playWhenReadyChangeReason; } if (pendingOperationAcks == 0) { Timeline newTimeline = playbackInfoUpdate.playbackInfo.timeline; if (!this.playbackInfo.timeline.isEmpty() && newTimeline.isEmpty()) { // Update the masking variables, which are used when the timeline becomes empty because a // ConcatenatingMediaSource has been cleared. maskingWindowIndex = C.INDEX_UNSET; maskingWindowPositionMs = 0; maskingPeriodIndex = 0; } if (!newTimeline.isEmpty()) { List timelines = ((PlaylistTimeline) newTimeline).getChildTimelines(); checkState(timelines.size() == mediaSourceHolderSnapshots.size()); for (int i = 0; i < timelines.size(); i++) { mediaSourceHolderSnapshots.get(i).timeline = timelines.get(i); } } boolean positionDiscontinuity = false; long discontinuityWindowStartPositionUs = C.TIME_UNSET; if (pendingDiscontinuity) { positionDiscontinuity = !playbackInfoUpdate.playbackInfo.periodId.equals(playbackInfo.periodId) || playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs != playbackInfo.positionUs; if (positionDiscontinuity) { discontinuityWindowStartPositionUs = newTimeline.isEmpty() || playbackInfoUpdate.playbackInfo.periodId.isAd() ? playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs : periodPositionUsToWindowPositionUs( newTimeline, playbackInfoUpdate.playbackInfo.periodId, playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs); } } pendingDiscontinuity = false; updatePlaybackInfo( playbackInfoUpdate.playbackInfo, TIMELINE_CHANGE_REASON_SOURCE_UPDATE, pendingPlayWhenReadyChangeReason, positionDiscontinuity, pendingDiscontinuityReason, discontinuityWindowStartPositionUs, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } } // Calling deprecated listeners. @SuppressWarnings(""deprecation"") private void updatePlaybackInfo( PlaybackInfo playbackInfo, @TimelineChangeReason int timelineChangeReason, @PlayWhenReadyChangeReason int playWhenReadyChangeReason, boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, long discontinuityWindowStartPositionUs, int oldMaskingMediaItemIndex, boolean repeatCurrentMediaItem) { // Assign playback info immediately such that all getters return the right values, but keep // snapshot of previous and new state so that listener invocations are triggered correctly. PlaybackInfo previousPlaybackInfo = this.playbackInfo; PlaybackInfo newPlaybackInfo = playbackInfo; this.playbackInfo = playbackInfo; boolean timelineChanged = !previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline); Pair mediaItemTransitionInfo = evaluateMediaItemTransitionReason( newPlaybackInfo, previousPlaybackInfo, positionDiscontinuity, positionDiscontinuityReason, timelineChanged, repeatCurrentMediaItem); boolean mediaItemTransitioned = mediaItemTransitionInfo.first; int mediaItemTransitionReason = mediaItemTransitionInfo.second; MediaMetadata newMediaMetadata = mediaMetadata; @Nullable MediaItem mediaItem = null; if (mediaItemTransitioned) { if (!newPlaybackInfo.timeline.isEmpty()) { int windowIndex = newPlaybackInfo.timeline.getPeriodByUid(newPlaybackInfo.periodId.periodUid, period) .windowIndex; mediaItem = newPlaybackInfo.timeline.getWindow(windowIndex, window).mediaItem; } staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; } if (mediaItemTransitioned || !previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) { staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata .buildUpon() .populateFromMetadata(newPlaybackInfo.staticMetadata) .build(); newMediaMetadata = buildUpdatedMediaMetadata(); } boolean metadataChanged = !newMediaMetadata.equals(mediaMetadata); mediaMetadata = newMediaMetadata; boolean playWhenReadyChanged = previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady; boolean playbackStateChanged = previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState; if (playbackStateChanged || playWhenReadyChanged) { updateWakeAndWifiLock(); } boolean isLoadingChanged = previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading; if (isLoadingChanged) { updatePriorityTaskManagerForIsLoadingChange(newPlaybackInfo.isLoading); } if (timelineChanged) { listeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason)); } if (positionDiscontinuity) { PositionInfo previousPositionInfo = getPreviousPositionInfo( positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, listener -> { listener.onPositionDiscontinuity(positionDiscontinuityReason); listener.onPositionDiscontinuity( previousPositionInfo, positionInfo, positionDiscontinuityReason); }); } if (mediaItemTransitioned) { @Nullable final MediaItem finalMediaItem = mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason)); } if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) { listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError)); if (newPlaybackInfo.playbackError != null) { listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(newPlaybackInfo.playbackError)); } } if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) { trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info); listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newPlaybackInfo.trackSelectorResult.tracks)); } if (metadataChanged) { final MediaMetadata finalMediaMetadata = mediaMetadata; listeners.queueEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(finalMediaMetadata)); } if (isLoadingChanged) { listeners.queueEvent( Player.EVENT_IS_LOADING_CHANGED, listener -> { listener.onLoadingChanged(newPlaybackInfo.isLoading); listener.onIsLoadingChanged(newPlaybackInfo.isLoading); }); } if (playbackStateChanged || playWhenReadyChanged) { listeners.queueEvent( /* eventFlag= */ C.INDEX_UNSET, listener -> listener.onPlayerStateChanged( newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState)); } if (playbackStateChanged) { listeners.queueEvent( Player.EVENT_PLAYBACK_STATE_CHANGED, listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState)); } if (playWhenReadyChanged) { listeners.queueEvent( Player.EVENT_PLAY_WHEN_READY_CHANGED, listener -> listener.onPlayWhenReadyChanged( newPlaybackInfo.playWhenReady, playWhenReadyChangeReason)); } if (previousPlaybackInfo.playbackSuppressionReason != newPlaybackInfo.playbackSuppressionReason) { listeners.queueEvent( Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, listener -> listener.onPlaybackSuppressionReasonChanged( newPlaybackInfo.playbackSuppressionReason)); } if (previousPlaybackInfo.isPlaying() != newPlaybackInfo.isPlaying()) { listeners.queueEvent( Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(newPlaybackInfo.isPlaying())); } if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) { listeners.queueEvent( Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters)); } updateAvailableCommands(); listeners.flushEvents(); if (previousPlaybackInfo.sleepingForOffload != newPlaybackInfo.sleepingForOffload) { for (AudioOffloadListener listener : audioOffloadListeners) { listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload); } } } private PositionInfo getPreviousPositionInfo( @DiscontinuityReason int positionDiscontinuityReason, PlaybackInfo oldPlaybackInfo, int oldMaskingMediaItemIndex) { @Nullable Object oldWindowUid = null; @Nullable Object oldPeriodUid = null; int oldMediaItemIndex = oldMaskingMediaItemIndex; int oldPeriodIndex = C.INDEX_UNSET; @Nullable MediaItem oldMediaItem = null; Timeline.Period oldPeriod = new Timeline.Period(); if (!oldPlaybackInfo.timeline.isEmpty()) { oldPeriodUid = oldPlaybackInfo.periodId.periodUid; oldPlaybackInfo.timeline.getPeriodByUid(oldPeriodUid, oldPeriod); oldMediaItemIndex = oldPeriod.windowIndex; oldPeriodIndex = oldPlaybackInfo.timeline.getIndexOfPeriod(oldPeriodUid); oldWindowUid = oldPlaybackInfo.timeline.getWindow(oldMediaItemIndex, window).uid; oldMediaItem = window.mediaItem; } long oldPositionUs; long oldContentPositionUs; if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { if (oldPlaybackInfo.periodId.isAd()) { // The old position is the end of the previous ad. oldPositionUs = oldPeriod.getAdDurationUs( oldPlaybackInfo.periodId.adGroupIndex, oldPlaybackInfo.periodId.adIndexInAdGroup); // The ad cue point is stored in the old requested content position. oldContentPositionUs = getRequestedContentPositionUs(oldPlaybackInfo); } else if (oldPlaybackInfo.periodId.nextAdGroupIndex != C.INDEX_UNSET) { // The old position is the end of a clipped content before an ad group. Use the exact ad // cue point as the transition position. oldPositionUs = getRequestedContentPositionUs(playbackInfo); oldContentPositionUs = oldPositionUs; } else { // The old position is the end of a Timeline period. Use the exact duration. oldPositionUs = oldPeriod.positionInWindowUs + oldPeriod.durationUs; oldContentPositionUs = oldPositionUs; } } else if (oldPlaybackInfo.periodId.isAd()) { oldPositionUs = oldPlaybackInfo.positionUs; oldContentPositionUs = getRequestedContentPositionUs(oldPlaybackInfo); } else { oldPositionUs = oldPeriod.positionInWindowUs + oldPlaybackInfo.positionUs; oldContentPositionUs = oldPositionUs; } return new PositionInfo( oldWindowUid, oldMediaItemIndex, oldMediaItem, oldPeriodUid, oldPeriodIndex, Util.usToMs(oldPositionUs), Util.usToMs(oldContentPositionUs), oldPlaybackInfo.periodId.adGroupIndex, oldPlaybackInfo.periodId.adIndexInAdGroup); } private PositionInfo getPositionInfo(long discontinuityWindowStartPositionUs) { @Nullable Object newWindowUid = null; @Nullable Object newPeriodUid = null; int newMediaItemIndex = getCurrentMediaItemIndex(); int newPeriodIndex = C.INDEX_UNSET; @Nullable MediaItem newMediaItem = null; if (!playbackInfo.timeline.isEmpty()) { newPeriodUid = playbackInfo.periodId.periodUid; playbackInfo.timeline.getPeriodByUid(newPeriodUid, period); newPeriodIndex = playbackInfo.timeline.getIndexOfPeriod(newPeriodUid); newWindowUid = playbackInfo.timeline.getWindow(newMediaItemIndex, window).uid; newMediaItem = window.mediaItem; } long positionMs = Util.usToMs(discontinuityWindowStartPositionUs); return new PositionInfo( newWindowUid, newMediaItemIndex, newMediaItem, newPeriodUid, newPeriodIndex, positionMs, /* contentPositionMs= */ playbackInfo.periodId.isAd() ? Util.usToMs(getRequestedContentPositionUs(playbackInfo)) : positionMs, playbackInfo.periodId.adGroupIndex, playbackInfo.periodId.adIndexInAdGroup); } private static long getRequestedContentPositionUs(PlaybackInfo playbackInfo) { Timeline.Window window = new Timeline.Window(); Timeline.Period period = new Timeline.Period(); playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET ? playbackInfo.timeline.getWindow(period.windowIndex, window).getDefaultPositionUs() : period.getPositionInWindowUs() + playbackInfo.requestedContentPositionUs; } private Pair evaluateMediaItemTransitionReason( PlaybackInfo playbackInfo, PlaybackInfo oldPlaybackInfo, boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, boolean timelineChanged, boolean repeatCurrentMediaItem) { Timeline oldTimeline = oldPlaybackInfo.timeline; Timeline newTimeline = playbackInfo.timeline; if (newTimeline.isEmpty() && oldTimeline.isEmpty()) { return new Pair<>(/* isTransitioning */ false, /* mediaItemTransitionReason */ C.INDEX_UNSET); } else if (newTimeline.isEmpty() != oldTimeline.isEmpty()) { return new Pair<>(/* isTransitioning */ true, MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); } int oldWindowIndex = oldTimeline.getPeriodByUid(oldPlaybackInfo.periodId.periodUid, period).windowIndex; Object oldWindowUid = oldTimeline.getWindow(oldWindowIndex, window).uid; int newWindowIndex = newTimeline.getPeriodByUid(playbackInfo.periodId.periodUid, period).windowIndex; Object newWindowUid = newTimeline.getWindow(newWindowIndex, window).uid; if (!oldWindowUid.equals(newWindowUid)) { @Player.MediaItemTransitionReason int transitionReason; if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { transitionReason = MEDIA_ITEM_TRANSITION_REASON_AUTO; } else if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK) { transitionReason = MEDIA_ITEM_TRANSITION_REASON_SEEK; } else if (timelineChanged) { transitionReason = MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; } else { // A change in window uid must be justified by one of the reasons above. throw new IllegalStateException(); } return new Pair<>(/* isTransitioning */ true, transitionReason); } else { // Only mark changes within the current item as a transition if we are repeating automatically // or via a seek to next/previous. if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION && oldPlaybackInfo.periodId.windowSequenceNumber < playbackInfo.periodId.windowSequenceNumber) { return new Pair<>(/* isTransitioning */ true, MEDIA_ITEM_TRANSITION_REASON_REPEAT); } if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK && repeatCurrentMediaItem) { return new Pair<>(/* isTransitioning */ true, MEDIA_ITEM_TRANSITION_REASON_SEEK); } } return new Pair<>(/* isTransitioning */ false, /* mediaItemTransitionReason */ C.INDEX_UNSET); } private void updateAvailableCommands() { Commands previousAvailableCommands = availableCommands; availableCommands = Util.getAvailableCommands(wrappingPlayer, permanentAvailableCommands); if (!availableCommands.equals(previousAvailableCommands)) { listeners.queueEvent( Player.EVENT_AVAILABLE_COMMANDS_CHANGED, listener -> listener.onAvailableCommandsChanged(availableCommands)); } } private void setMediaSourcesInternal( List mediaSources, int startWindowIndex, long startPositionMs, boolean resetToDefaultPosition) { int currentWindowIndex = getCurrentWindowIndexInternal(playbackInfo); long currentPositionMs = getCurrentPosition(); pendingOperationAcks++; if (!mediaSourceHolderSnapshots.isEmpty()) { removeMediaSourceHolders( /* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size()); } List holders = addMediaSourceHolders(/* index= */ 0, mediaSources); Timeline timeline = createMaskingTimeline(); if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) { throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs); } // Evaluate the actual start position. if (resetToDefaultPosition) { startWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); startPositionMs = C.TIME_UNSET; } else if (startWindowIndex == C.INDEX_UNSET) { startWindowIndex = currentWindowIndex; startPositionMs = currentPositionMs; } PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, timeline, maskWindowPositionMsOrGetPeriodPositionUs(timeline, startWindowIndex, startPositionMs)); // Mask the playback state. int maskingPlaybackState = newPlaybackInfo.playbackState; if (startWindowIndex != C.INDEX_UNSET && newPlaybackInfo.playbackState != STATE_IDLE) { // Position reset to startWindowIndex (results in pending initial seek). if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) { // Setting an empty timeline or invalid seek transitions to ended. maskingPlaybackState = STATE_ENDED; } else { maskingPlaybackState = STATE_BUFFERING; } } newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState); internalPlayer.setMediaSources( holders, startWindowIndex, Util.msToUs(startPositionMs), shuffleOrder); boolean positionDiscontinuity = !playbackInfo.periodId.periodUid.equals(newPlaybackInfo.periodId.periodUid) && !playbackInfo.timeline.isEmpty(); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ positionDiscontinuity, DISCONTINUITY_REASON_REMOVE, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } private List addMediaSourceHolders( int index, List mediaSources) { List holders = new ArrayList<>(); for (int i = 0; i < mediaSources.size(); i++) { MediaSourceList.MediaSourceHolder holder = new MediaSourceList.MediaSourceHolder(mediaSources.get(i), useLazyPreparation); holders.add(holder); mediaSourceHolderSnapshots.add( i + index, new MediaSourceHolderSnapshot(holder.uid, holder.mediaSource.getTimeline())); } shuffleOrder = shuffleOrder.cloneAndInsert( /* insertionIndex= */ index, /* insertionCount= */ holders.size()); return holders; } private PlaybackInfo addMediaSourcesInternal( PlaybackInfo playbackInfo, int index, List mediaSources) { Timeline oldTimeline = playbackInfo.timeline; pendingOperationAcks++; List holders = addMediaSourceHolders(index, mediaSources); Timeline newTimeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, newTimeline, getPeriodPositionUsAfterTimelineChanged( oldTimeline, newTimeline, getCurrentWindowIndexInternal(playbackInfo), getContentPositionInternal(playbackInfo))); internalPlayer.addMediaSources(index, holders, shuffleOrder); return newPlaybackInfo; } private PlaybackInfo removeMediaItemsInternal( PlaybackInfo playbackInfo, int fromIndex, int toIndex) { int currentIndex = getCurrentWindowIndexInternal(playbackInfo); long contentPositionMs = getContentPositionInternal(playbackInfo); Timeline oldTimeline = playbackInfo.timeline; int currentMediaSourceCount = mediaSourceHolderSnapshots.size(); pendingOperationAcks++; removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex); Timeline newTimeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, newTimeline, getPeriodPositionUsAfterTimelineChanged( oldTimeline, newTimeline, currentIndex, contentPositionMs)); // Player transitions to STATE_ENDED if the current index is part of the removed tail. final boolean transitionsToEnded = newPlaybackInfo.playbackState != STATE_IDLE && newPlaybackInfo.playbackState != STATE_ENDED && fromIndex < toIndex && toIndex == currentMediaSourceCount && currentIndex >= newPlaybackInfo.timeline.getWindowCount(); if (transitionsToEnded) { newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(STATE_ENDED); } internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder); return newPlaybackInfo; } private void removeMediaSourceHolders(int fromIndex, int toIndexExclusive) { for (int i = toIndexExclusive - 1; i >= fromIndex; i--) { mediaSourceHolderSnapshots.remove(i); } shuffleOrder = shuffleOrder.cloneAndRemove(fromIndex, toIndexExclusive); } private Timeline createMaskingTimeline() { return new PlaylistTimeline(mediaSourceHolderSnapshots, shuffleOrder); } private PlaybackInfo maskTimelineAndPosition( PlaybackInfo playbackInfo, Timeline timeline, @Nullable Pair periodPositionUs) { checkArgument(timeline.isEmpty() || periodPositionUs != null); // Get the old timeline and position before updating playbackInfo. Timeline oldTimeline = playbackInfo.timeline; long oldContentPositionMs = getContentPositionInternal(playbackInfo); // Mask the timeline. playbackInfo = playbackInfo.copyWithTimeline(timeline); if (timeline.isEmpty()) { // Reset periodId and loadingPeriodId. MediaPeriodId dummyMediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline(); long positionUs = Util.msToUs(maskingWindowPositionMs); playbackInfo = playbackInfo.copyWithNewPosition( dummyMediaPeriodId, positionUs, /* requestedContentPositionUs= */ positionUs, /* discontinuityStartPositionUs= */ positionUs, /* totalBufferedDurationUs= */ 0, TrackGroupArray.EMPTY, emptyTrackSelectorResult, /* staticMetadata= */ ImmutableList.of()); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(dummyMediaPeriodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; return playbackInfo; } Object oldPeriodUid = playbackInfo.periodId.periodUid; boolean playingPeriodChanged = !oldPeriodUid.equals(castNonNull(periodPositionUs).first); MediaPeriodId newPeriodId = playingPeriodChanged ? new MediaPeriodId(periodPositionUs.first) : playbackInfo.periodId; long newContentPositionUs = periodPositionUs.second; long oldContentPositionUs = Util.msToUs(oldContentPositionMs); if (!oldTimeline.isEmpty()) { oldContentPositionUs -= oldTimeline.getPeriodByUid(oldPeriodUid, period).getPositionInWindowUs(); } if (playingPeriodChanged || newContentPositionUs < oldContentPositionUs) { checkState(!newPeriodId.isAd()); // The playing period changes or a backwards seek within the playing period occurs. playbackInfo = playbackInfo.copyWithNewPosition( newPeriodId, /* positionUs= */ newContentPositionUs, /* requestedContentPositionUs= */ newContentPositionUs, /* discontinuityStartPositionUs= */ newContentPositionUs, /* totalBufferedDurationUs= */ 0, playingPeriodChanged ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, playingPeriodChanged ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, playingPeriodChanged ? ImmutableList.of() : playbackInfo.staticMetadata); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId); playbackInfo.bufferedPositionUs = newContentPositionUs; } else if (newContentPositionUs == oldContentPositionUs) { // Period position remains unchanged. int loadingPeriodIndex = timeline.getIndexOfPeriod(playbackInfo.loadingMediaPeriodId.periodUid); if (loadingPeriodIndex == C.INDEX_UNSET || timeline.getPeriod(loadingPeriodIndex, period).windowIndex != timeline.getPeriodByUid(newPeriodId.periodUid, period).windowIndex) { // Discard periods after the playing period, if the loading period is discarded or the // playing and loading period are not in the same window. timeline.getPeriodByUid(newPeriodId.periodUid, period); long maskedBufferedPositionUs = newPeriodId.isAd() ? period.getAdDurationUs(newPeriodId.adGroupIndex, newPeriodId.adIndexInAdGroup) : period.durationUs; playbackInfo = playbackInfo.copyWithNewPosition( newPeriodId, /* positionUs= */ playbackInfo.positionUs, /* requestedContentPositionUs= */ playbackInfo.positionUs, playbackInfo.discontinuityStartPositionUs, /* totalBufferedDurationUs= */ maskedBufferedPositionUs - playbackInfo.positionUs, playbackInfo.trackGroups, playbackInfo.trackSelectorResult, playbackInfo.staticMetadata); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId); playbackInfo.bufferedPositionUs = maskedBufferedPositionUs; } } else { checkState(!newPeriodId.isAd()); // A forward seek within the playing period (timeline did not change). long maskedTotalBufferedDurationUs = max( 0, playbackInfo.totalBufferedDurationUs - (newContentPositionUs - oldContentPositionUs)); long maskedBufferedPositionUs = playbackInfo.bufferedPositionUs; if (playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId)) { maskedBufferedPositionUs = newContentPositionUs + maskedTotalBufferedDurationUs; } playbackInfo = playbackInfo.copyWithNewPosition( newPeriodId, /* positionUs= */ newContentPositionUs, /* requestedContentPositionUs= */ newContentPositionUs, /* discontinuityStartPositionUs= */ newContentPositionUs, maskedTotalBufferedDurationUs, playbackInfo.trackGroups, playbackInfo.trackSelectorResult, playbackInfo.staticMetadata); playbackInfo.bufferedPositionUs = maskedBufferedPositionUs; } return playbackInfo; } @Nullable private Pair getPeriodPositionUsAfterTimelineChanged( Timeline oldTimeline, Timeline newTimeline, int [MASK] , long contentPositionMs) { if (oldTimeline.isEmpty() || newTimeline.isEmpty()) { boolean isCleared = !oldTimeline.isEmpty() && newTimeline.isEmpty(); return maskWindowPositionMsOrGetPeriodPositionUs( newTimeline, isCleared ? C.INDEX_UNSET : [MASK] , isCleared ? C.TIME_UNSET : contentPositionMs); } @Nullable Pair oldPeriodPositionUs = oldTimeline.getPeriodPositionUs( window, period, [MASK] , Util.msToUs(contentPositionMs)); Object periodUid = castNonNull(oldPeriodPositionUs).first; if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) { // The old period position is still available in the new timeline. return oldPeriodPositionUs; } // Period uid not found in new timeline. Try to get subsequent period. @Nullable Object nextPeriodUid = ExoPlayerImplInternal.resolveSubsequentPeriod( window, period, repeatMode, shuffleModeEnabled, periodUid, oldTimeline, newTimeline); if (nextPeriodUid != null) { // Reset position to the default position of the window of the subsequent period. newTimeline.getPeriodByUid(nextPeriodUid, period); return maskWindowPositionMsOrGetPeriodPositionUs( newTimeline, period.windowIndex, newTimeline.getWindow(period.windowIndex, window).getDefaultPositionMs()); } else { // No subsequent period found and the new timeline is not empty. Use the default position. return maskWindowPositionMsOrGetPeriodPositionUs( newTimeline, /* windowIndex= */ C.INDEX_UNSET, /* windowPositionMs= */ C.TIME_UNSET); } } @Nullable private Pair maskWindowPositionMsOrGetPeriodPositionUs( Timeline timeline, int windowIndex, long windowPositionMs) { if (timeline.isEmpty()) { // If empty we store the initial seek in the masking variables. maskingWindowIndex = windowIndex; maskingWindowPositionMs = windowPositionMs == C.TIME_UNSET ? 0 : windowPositionMs; maskingPeriodIndex = 0; return null; } if (windowIndex == C.INDEX_UNSET || windowIndex >= timeline.getWindowCount()) { // Use default position of timeline if window index still unset or if a previous initial seek // now turns out to be invalid. windowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); windowPositionMs = timeline.getWindow(windowIndex, window).getDefaultPositionMs(); } return timeline.getPeriodPositionUs(window, period, windowIndex, Util.msToUs(windowPositionMs)); } private long periodPositionUsToWindowPositionUs( Timeline timeline, MediaPeriodId periodId, long positionUs) { timeline.getPeriodByUid(periodId.periodUid, period); positionUs += period.getPositionInWindowUs(); return positionUs; } private PlayerMessage createMessageInternal(Target target) { int currentWindowIndex = getCurrentWindowIndexInternal(playbackInfo); return new PlayerMessage( internalPlayer, target, playbackInfo.timeline, currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex, clock, internalPlayer.getPlaybackLooper()); } /** * Builds a {@link MediaMetadata} from the main sources. * *

{@link MediaItem} {@link MediaMetadata} is prioritized, with any gaps/missing fields * populated by metadata from static ({@link TrackGroup} {@link Format}) and dynamic ({@link * MetadataOutput#onMetadata(Metadata)}) sources. */ private MediaMetadata buildUpdatedMediaMetadata() { Timeline timeline = getCurrentTimeline(); if (timeline.isEmpty()) { return staticAndDynamicMediaMetadata; } MediaItem mediaItem = timeline.getWindow(getCurrentMediaItemIndex(), window).mediaItem; // MediaItem metadata is prioritized over metadata within the media. return staticAndDynamicMediaMetadata.buildUpon().populate(mediaItem.mediaMetadata).build(); } private void removeSurfaceCallbacks() { if (sphericalGLSurfaceView != null) { createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) .setPayload(null) .send(); sphericalGLSurfaceView.removeVideoSurfaceListener(componentListener); sphericalGLSurfaceView = null; } if (textureView != null) { if (textureView.getSurfaceTextureListener() != componentListener) { Log.w(TAG, ""SurfaceTextureListener already unset or replaced.""); } else { textureView.setSurfaceTextureListener(null); } textureView = null; } if (surfaceHolder != null) { surfaceHolder.removeCallback(componentListener); surfaceHolder = null; } } private void setSurfaceTextureInternal(SurfaceTexture surfaceTexture) { Surface surface = new Surface(surfaceTexture); setVideoOutputInternal(surface); ownedSurface = surface; } private void setVideoOutputInternal(@Nullable Object videoOutput) { // Note: We don't turn this method into a no-op if the output is being replaced with itself so // as to ensure onRenderedFirstFrame callbacks are still called in this case. List messages = new ArrayList<>(); for (Renderer renderer : renderers) { if (renderer.getTrackType() == TRACK_TYPE_VIDEO) { messages.add( createMessageInternal(renderer) .setType(MSG_SET_VIDEO_OUTPUT) .setPayload(videoOutput) .send()); } } boolean messageDeliveryTimedOut = false; if (this.videoOutput != null && this.videoOutput != videoOutput) { // We're replacing an output. Block to ensure that this output will not be accessed by the // renderers after this method returns. try { for (PlayerMessage message : messages) { message.blockUntilDelivered(detachSurfaceTimeoutMs); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (TimeoutException e) { messageDeliveryTimedOut = true; } if (this.videoOutput == ownedSurface) { // We're replacing a surface that we are responsible for releasing. ownedSurface.release(); ownedSurface = null; } } this.videoOutput = videoOutput; if (messageDeliveryTimedOut) { stopInternal( ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE), PlaybackException.ERROR_CODE_TIMEOUT)); } } /** * Sets the holder of the surface that will be displayed to the user, but which should * not be the output for video renderers. This case occurs when video frames need to be * rendered to an intermediate surface (which is not the one held by the provided holder). * * @param nonVideoOutputSurfaceHolder The holder of the surface that will eventually be displayed * to the user. */ private void setNonVideoOutputSurfaceHolderInternal(SurfaceHolder nonVideoOutputSurfaceHolder) { // Although we won't use the view's surface directly as the video output, still use the holder // to query the surface size, to be informed in changes to the size via componentListener, and // for equality checking in clearVideoSurfaceHolder. surfaceHolderSurfaceIsVideoOutput = false; surfaceHolder = nonVideoOutputSurfaceHolder; surfaceHolder.addCallback(componentListener); Surface surface = surfaceHolder.getSurface(); if (surface != null && surface.isValid()) { Rect surfaceSize = surfaceHolder.getSurfaceFrame(); maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height()); } else { maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } } private void maybeNotifySurfaceSizeChanged(int width, int height) { if (width != surfaceSize.getWidth() || height != surfaceSize.getHeight()) { surfaceSize = new Size(width, height); listeners.sendEvent( EVENT_SURFACE_SIZE_CHANGED, listener -> listener.onSurfaceSizeChanged(width, height)); sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_VIDEO_OUTPUT_RESOLUTION, new Size(width, height)); } } private void sendVolumeToRenderers() { float scaledVolume = volume * audioFocusManager.getVolumeMultiplier(); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_VOLUME, scaledVolume); } private void updatePlayWhenReady( boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand, @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) { playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY; @PlaybackSuppressionReason int playbackSuppressionReason = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY ? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS : Player.PLAYBACK_SUPPRESSION_REASON_NONE; if (playbackInfo.playWhenReady == playWhenReady && playbackInfo.playbackSuppressionReason == playbackSuppressionReason) { return; } pendingOperationAcks++; // Position estimation and copy must occur before changing/masking playback state. PlaybackInfo playbackInfo = this.playbackInfo.sleepingForOffload ? this.playbackInfo.copyWithEstimatedPosition() : this.playbackInfo; playbackInfo = playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason); internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason); updatePlaybackInfo( playbackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, playWhenReadyChangeReason, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } private void updateWakeAndWifiLock() { @State int playbackState = getPlaybackState(); switch (playbackState) { case Player.STATE_READY: case Player.STATE_BUFFERING: boolean isSleeping = experimentalIsSleepingForOffload(); wakeLockManager.setStayAwake(getPlayWhenReady() && !isSleeping); // The wifi lock is not released while sleeping to avoid interrupting downloads. wifiLockManager.setStayAwake(getPlayWhenReady()); break; case Player.STATE_ENDED: case Player.STATE_IDLE: wakeLockManager.setStayAwake(false); wifiLockManager.setStayAwake(false); break; default: throw new IllegalStateException(); } } private void verifyApplicationThread() { // The constructor may be executed on a background thread. Wait with accessing the player from // the app thread until the constructor finished executing. constructorFinished.blockUninterruptible(); if (Thread.currentThread() != getApplicationLooper().getThread()) { String message = Util.formatInvariant( ""Player is accessed on the wrong thread.\n"" + ""Current thread: '%s'\n"" + ""Expected thread: '%s'\n"" + ""See https://developer.android.com/guide/topics/media/issues/"" + ""player-accessed-on-wrong-thread"", Thread.currentThread().getName(), getApplicationLooper().getThread().getName()); if (throwsWhenUsingWrongThread) { throw new IllegalStateException(message); } Log.w(TAG, message, hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException()); hasNotifiedFullWrongThreadWarning = true; } } private void sendRendererMessage( @C.TrackType int trackType, int messageType, @Nullable Object payload) { for (Renderer renderer : renderers) { if (renderer.getTrackType() == trackType) { createMessageInternal(renderer).setType(messageType).setPayload(payload).send(); } } } /** * Initializes {@link #keepSessionIdAudioTrack} to keep an audio session ID alive. If the audio * session ID is {@link C#AUDIO_SESSION_ID_UNSET} then a new audio session ID is generated. * *

Use of this method is only required on API level 21 and earlier. * * @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} to generate a * new one. * @return The audio session ID. */ private int initializeKeepSessionIdAudioTrack(int audioSessionId) { if (keepSessionIdAudioTrack != null && keepSessionIdAudioTrack.getAudioSessionId() != audioSessionId) { keepSessionIdAudioTrack.release(); keepSessionIdAudioTrack = null; } if (keepSessionIdAudioTrack == null) { int sampleRate = 4000; // Minimum sample rate supported by the platform. int channelConfig = AudioFormat.CHANNEL_OUT_MONO; @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. keepSessionIdAudioTrack = new AudioTrack( C.STREAM_TYPE_DEFAULT, sampleRate, channelConfig, encoding, bufferSize, AudioTrack.MODE_STATIC, audioSessionId); } return keepSessionIdAudioTrack.getAudioSessionId(); } private void updatePriorityTaskManagerForIsLoadingChange(boolean isLoading) { if (priorityTaskManager != null) { if (isLoading && !isPriorityTaskManagerRegistered) { priorityTaskManager.add(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = true; } else if (!isLoading && isPriorityTaskManagerRegistered) { priorityTaskManager.remove(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = false; } } } private static DeviceInfo createDeviceInfo(@Nullable StreamVolumeManager streamVolumeManager) { return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL) .setMinVolume(streamVolumeManager != null ? streamVolumeManager.getMinVolume() : 0) .setMaxVolume(streamVolumeManager != null ? streamVolumeManager.getMaxVolume() : 0) .build(); } private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) { return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY ? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS : PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; } private static final class MediaSourceHolderSnapshot implements MediaSourceInfoHolder { private final Object uid; private Timeline timeline; public MediaSourceHolderSnapshot(Object uid, Timeline timeline) { this.uid = uid; this.timeline = timeline; } @Override public Object getUid() { return uid; } @Override public Timeline getTimeline() { return timeline; } } private final class ComponentListener implements VideoRendererEventListener, AudioRendererEventListener, TextOutput, MetadataOutput, SurfaceHolder.Callback, TextureView.SurfaceTextureListener, SphericalGLSurfaceView.VideoSurfaceListener, AudioFocusManager.PlayerControl, AudioBecomingNoisyManager.EventListener, StreamVolumeManager.Listener, AudioOffloadListener { // VideoRendererEventListener implementation @Override public void onVideoEnabled(DecoderCounters counters) { videoDecoderCounters = counters; analyticsCollector.onVideoEnabled(counters); } @Override public void onVideoDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) { analyticsCollector.onVideoDecoderInitialized( decoderName, initializedTimestampMs, initializationDurationMs); } @Override public void onVideoInputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { videoFormat = format; analyticsCollector.onVideoInputFormatChanged(format, decoderReuseEvaluation); } @Override public void onDroppedFrames(int count, long elapsed) { analyticsCollector.onDroppedFrames(count, elapsed); } @Override public void onVideoSizeChanged(VideoSize newVideoSize) { videoSize = newVideoSize; listeners.sendEvent( EVENT_VIDEO_SIZE_CHANGED, listener -> listener.onVideoSizeChanged(newVideoSize)); } @Override public void onRenderedFirstFrame(Object output, long renderTimeMs) { analyticsCollector.onRenderedFirstFrame(output, renderTimeMs); if (videoOutput == output) { listeners.sendEvent(EVENT_RENDERED_FIRST_FRAME, Listener::onRenderedFirstFrame); } } @Override public void onVideoDecoderReleased(String decoderName) { analyticsCollector.onVideoDecoderReleased(decoderName); } @Override public void onVideoDisabled(DecoderCounters counters) { analyticsCollector.onVideoDisabled(counters); videoFormat = null; videoDecoderCounters = null; } @Override public void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) { analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount); } @Override public void onVideoCodecError(Exception videoCodecError) { analyticsCollector.onVideoCodecError(videoCodecError); } // AudioRendererEventListener implementation @Override public void onAudioEnabled(DecoderCounters counters) { audioDecoderCounters = counters; analyticsCollector.onAudioEnabled(counters); } @Override public void onAudioDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) { analyticsCollector.onAudioDecoderInitialized( decoderName, initializedTimestampMs, initializationDurationMs); } @Override public void onAudioInputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { audioFormat = format; analyticsCollector.onAudioInputFormatChanged(format, decoderReuseEvaluation); } @Override public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) { analyticsCollector.onAudioPositionAdvancing(playoutStartSystemTimeMs); } @Override public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { analyticsCollector.onAudioUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); } @Override public void onAudioDecoderReleased(String decoderName) { analyticsCollector.onAudioDecoderReleased(decoderName); } @Override public void onAudioDisabled(DecoderCounters counters) { analyticsCollector.onAudioDisabled(counters); audioFormat = null; audioDecoderCounters = null; } @Override public void onSkipSilenceEnabledChanged(boolean newSkipSilenceEnabled) { if (skipSilenceEnabled == newSkipSilenceEnabled) { return; } skipSilenceEnabled = newSkipSilenceEnabled; listeners.sendEvent( EVENT_SKIP_SILENCE_ENABLED_CHANGED, listener -> listener.onSkipSilenceEnabledChanged(newSkipSilenceEnabled)); } @Override public void onAudioSinkError(Exception audioSinkError) { analyticsCollector.onAudioSinkError(audioSinkError); } @Override public void onAudioCodecError(Exception audioCodecError) { analyticsCollector.onAudioCodecError(audioCodecError); } // TextOutput implementation @Override public void onCues(List cues) { listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cues)); } @Override public void onCues(CueGroup cueGroup) { currentCueGroup = cueGroup; listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cueGroup)); } // MetadataOutput implementation @Override public void onMetadata(Metadata metadata) { staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build(); MediaMetadata newMediaMetadata = buildUpdatedMediaMetadata(); if (!newMediaMetadata.equals(mediaMetadata)) { mediaMetadata = newMediaMetadata; listeners.queueEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(mediaMetadata)); } listeners.queueEvent(EVENT_METADATA, listener -> listener.onMetadata(metadata)); listeners.flushEvents(); } // SurfaceHolder.Callback implementation @Override public void surfaceCreated(SurfaceHolder holder) { if (surfaceHolderSurfaceIsVideoOutput) { setVideoOutputInternal(holder.getSurface()); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { maybeNotifySurfaceSizeChanged(width, height); } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (surfaceHolderSurfaceIsVideoOutput) { setVideoOutputInternal(/* videoOutput= */ null); } maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } // TextureView.SurfaceTextureListener implementation @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { setSurfaceTextureInternal(surfaceTexture); maybeNotifySurfaceSizeChanged(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { maybeNotifySurfaceSizeChanged(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { // Do nothing. } // SphericalGLSurfaceView.VideoSurfaceListener @Override public void onVideoSurfaceCreated(Surface surface) { setVideoOutputInternal(surface); } @Override public void onVideoSurfaceDestroyed(Surface surface) { setVideoOutputInternal(/* videoOutput= */ null); } // AudioFocusManager.PlayerControl implementation @Override public void setVolumeMultiplier(float volumeMultiplier) { sendVolumeToRenderers(); } @Override public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) { boolean playWhenReady = getPlayWhenReady(); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); } // AudioBecomingNoisyManager.EventListener implementation. @Override public void onAudioBecomingNoisy() { updatePlayWhenReady( /* playWhenReady= */ false, AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY); } // StreamVolumeManager.Listener implementation. @Override public void onStreamTypeChanged(@C.StreamType int streamType) { DeviceInfo newDeviceInfo = createDeviceInfo(streamVolumeManager); if (!newDeviceInfo.equals(deviceInfo)) { deviceInfo = newDeviceInfo; listeners.sendEvent( EVENT_DEVICE_INFO_CHANGED, listener -> listener.onDeviceInfoChanged(newDeviceInfo)); } } @Override public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) { listeners.sendEvent( EVENT_DEVICE_VOLUME_CHANGED, listener -> listener.onDeviceVolumeChanged(streamVolume, streamMuted)); } // Player.AudioOffloadListener implementation. @Override public void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) { updateWakeAndWifiLock(); } } /** Listeners that are called on the playback thread. */ private static final class FrameMetadataListener implements VideoFrameMetadataListener, CameraMotionListener, PlayerMessage.Target { public static final @MessageType int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; public static final @MessageType int MSG_SET_CAMERA_MOTION_LISTENER = Renderer.MSG_SET_CAMERA_MOTION_LISTENER; public static final @MessageType int MSG_SET_SPHERICAL_SURFACE_VIEW = Renderer.MSG_CUSTOM_BASE; @Nullable private VideoFrameMetadataListener videoFrameMetadataListener; @Nullable private CameraMotionListener cameraMotionListener; @Nullable private VideoFrameMetadataListener internalVideoFrameMetadataListener; @Nullable private CameraMotionListener internalCameraMotionListener; @Override public void handleMessage(@MessageType int messageType, @Nullable Object message) { switch (messageType) { case MSG_SET_VIDEO_FRAME_METADATA_LISTENER: videoFrameMetadataListener = (VideoFrameMetadataListener) message; break; case MSG_SET_CAMERA_MOTION_LISTENER: cameraMotionListener = (CameraMotionListener) message; break; case MSG_SET_SPHERICAL_SURFACE_VIEW: @Nullable SphericalGLSurfaceView surfaceView = (SphericalGLSurfaceView) message; if (surfaceView == null) { internalVideoFrameMetadataListener = null; internalCameraMotionListener = null; } else { internalVideoFrameMetadataListener = surfaceView.getVideoFrameMetadataListener(); internalCameraMotionListener = surfaceView.getCameraMotionListener(); } break; case Renderer.MSG_SET_AUDIO_ATTRIBUTES: case Renderer.MSG_SET_AUDIO_SESSION_ID: case Renderer.MSG_SET_AUX_EFFECT_INFO: case Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY: case Renderer.MSG_SET_SCALING_MODE: case Renderer.MSG_SET_SKIP_SILENCE_ENABLED: case Renderer.MSG_SET_VIDEO_OUTPUT: case Renderer.MSG_SET_VOLUME: case Renderer.MSG_SET_WAKEUP_LISTENER: default: break; } } // VideoFrameMetadataListener @Override public void onVideoFrameAboutToBeRendered( long presentationTimeUs, long releaseTimeNs, Format format, @Nullable MediaFormat mediaFormat) { if (internalVideoFrameMetadataListener != null) { internalVideoFrameMetadataListener.onVideoFrameAboutToBeRendered( presentationTimeUs, releaseTimeNs, format, mediaFormat); } if (videoFrameMetadataListener != null) { videoFrameMetadataListener.onVideoFrameAboutToBeRendered( presentationTimeUs, releaseTimeNs, format, mediaFormat); } } // CameraMotionListener @Override public void onCameraMotion(long timeUs, float[] rotation) { if (internalCameraMotionListener != null) { internalCameraMotionListener.onCameraMotion(timeUs, rotation); } if (cameraMotionListener != null) { cameraMotionListener.onCameraMotion(timeUs, rotation); } } @Override public void onCameraMotionReset() { if (internalCameraMotionListener != null) { internalCameraMotionListener.onCameraMotionReset(); } if (cameraMotionListener != null) { cameraMotionListener.onCameraMotionReset(); } } } @RequiresApi(31) private static final class Api31 { private Api31() {} @DoNotInline public static PlayerId registerMediaMetricsListener( Context context, ExoPlayerImpl player, boolean usePlatformDiagnostics) { @Nullable MediaMetricsListener listener = MediaMetricsListener.create(context); if (listener == null) { Log.w(TAG, ""MediaMetricsService unavailable.""); return new PlayerId(LogSessionId.LOG_SESSION_ID_NONE); } if (usePlatformDiagnostics) { player.addAnalyticsListener(listener); } return new PlayerId(listener.getLogSessionId()); } } } ","currentWindowIndexInternal " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.spring.boot.actuate.endpoint.metadata; import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.spring.ServiceBean; import org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor; import org.apache.dubbo.config.spring.util.DubboBeanUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import static org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors; import static org.springframework.util.ClassUtils.isPrimitiveOrWrapper; /** * Abstract Dubbo Meatadata * * @since 2.7.0 */ public abstract class AbstractDubboMetadata implements ApplicationContextAware, EnvironmentAware { protected ApplicationContext applicationContext; protected ConfigurableEnvironment environment; private static boolean isSimpleType(Class type) { return isPrimitiveOrWrapper(type) || type == String.class || type == BigDecimal.class || type == BigInteger.class || type == Date.class || type == URL.class || type == Class.class ; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void setEnvironment(Environment environment) { if (environment instanceof ConfigurableEnvironment) { this.environment = (ConfigurableEnvironment) environment; } } protected Map resolveBeanMetadata(final Object bean) { final Map [MASK] = new LinkedHashMap<>(); try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { Method readMethod = propertyDescriptor.getReadMethod(); if (readMethod != null && isSimpleType(propertyDescriptor.getPropertyType())) { String name = Introspector.decapitalize(propertyDescriptor.getName()); Object value = readMethod.invoke(bean); if (value != null) { [MASK] .put(name, value); } } } } catch (Exception e) { throw new RuntimeException(e); } return [MASK] ; } protected Map getServiceBeansMap() { return beansOfTypeIncludingAncestors(applicationContext, ServiceBean.class); } protected ReferenceAnnotationBeanPostProcessor getReferenceAnnotationBeanPostProcessor() { return DubboBeanUtils.getReferenceAnnotationBeanPostProcessor(applicationContext); } protected Map getProtocolConfigsBeanMap() { return beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class); } } ","beanMetadata " "/* * The MIT License * * Copyright (c) 2013-2014, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the ""Software""), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package jenkins.widgets; import hudson.model.Action; import hudson.model.Build; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.model.Job; import hudson.model.MockItem; import hudson.model.ModelObject; import hudson.model.ParameterValue; import hudson.model.ParametersAction; import hudson.model.Result; import hudson.model.Run; import hudson.model.StringParameterValue; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import jenkins.model.queue.QueueItem; import org.junit.Assert; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.mockito.Mockito; /** * @author tom.fennelly@gmail.com * * See also HistoryPageFilterInsensitiveSearchTest integration test. */ public class HistoryPageFilterTest { /** * No items. */ @Test public void test_latest_empty_page() { HistoryPageFilter historyPageFilter = newPage(5, null, null); Iterable [MASK] = Collections.emptyList(); historyPageFilter.add( [MASK] ); Assert.assertFalse(historyPageFilter.hasUpPage); Assert.assertFalse(historyPageFilter.hasDownPage); Assert.assertTrue(historyPageFilter.queueItems.isEmpty()); Assert.assertTrue(historyPageFilter.runs.isEmpty()); } /** * Latest/top page where total number of items less than the max page size. */ @Test public void test_latest_partial_page() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, null, null); Iterable runs = newRuns(1, 2); var queueItems = newQueueItems(3, 4); historyPageFilter.add(runs, queueItems); Assert.assertFalse(historyPageFilter.hasUpPage); Assert.assertFalse(historyPageFilter.hasDownPage); Assert.assertEquals(2, historyPageFilter.queueItems.size()); Assert.assertEquals(2, historyPageFilter.runs.size()); Assert.assertEquals(4, historyPageFilter.queueItems.get(0).getEntryId()); Assert.assertEquals(4, historyPageFilter.newestOnPage); Assert.assertEquals(3, historyPageFilter.queueItems.get(1).getEntryId()); Assert.assertEquals(HistoryPageEntry.getEntryId(2), historyPageFilter.runs.get(0).getEntryId()); Assert.assertEquals(HistoryPageEntry.getEntryId(1), historyPageFilter.runs.get(1).getEntryId()); Assert.assertEquals(HistoryPageEntry.getEntryId(1), historyPageFilter.oldestOnPage); } /** * Latest/top page where total number of items greater than the max page size. */ @Test public void test_latest_longer_list() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, null, null); Iterable runs = newRuns(1, 10); var queueItems = newQueueItems(11, 12); historyPageFilter.add(runs, queueItems); Assert.assertFalse(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(2, historyPageFilter.queueItems.size()); Assert.assertEquals(3, historyPageFilter.runs.size()); Assert.assertEquals(12, historyPageFilter.queueItems.get(0).getEntryId()); Assert.assertEquals(12, historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(10), historyPageFilter.runs.get(0).getEntryId()); } /** * Test olderThan (page down) when set to id greater than newest (should never happen). Should be same as not * specifying newerThan/olderThan. */ @Test public void test_olderThan_gt_newest() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, null, 11L); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertFalse(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(5, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(10), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(6), historyPageFilter.oldestOnPage); } /** * Test olderThan (page down) when set to id less than the oldest (should never happen). Should just give an * empty list of builds. */ @Test public void test_olderThan_lt_oldest() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, null, 0L); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertTrue(historyPageFilter.hasUpPage); Assert.assertFalse(historyPageFilter.hasDownPage); Assert.assertEquals(0, historyPageFilter.runs.size()); } /** * Test olderThan (page down) when set to an id close to the oldest in the list (where * there's less than a full page older than the supplied olderThan arg). */ @Test public void test_olderThan_leaving_part_page() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, null, 4L); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertTrue(historyPageFilter.hasUpPage); Assert.assertFalse(historyPageFilter.hasDownPage); // Should only be 3 runs on the page (oldest 3) Assert.assertEquals(3, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(3), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(1), historyPageFilter.oldestOnPage); } /** * Test olderThan (page down) when set to an id in the middle. Should be a page up and a page down. */ @Test public void test_olderThan_mid_page() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, null, 8L); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertTrue(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(5, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(7), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(3), historyPageFilter.oldestOnPage); } /** * Test newerThan (page up) when set to id greater than newest (should never happen). Should be an empty list. */ @Test public void test_newerThan_gt_newest() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, 11L, null); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertFalse(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(0, historyPageFilter.runs.size()); } /** * Test newerThan (page up) when set to id less than the oldest (should never happen). Should give the oldest * set of builds. */ @Test public void test_newerThan_lt_oldest() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, 0L, null); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertTrue(historyPageFilter.hasUpPage); Assert.assertFalse(historyPageFilter.hasDownPage); Assert.assertEquals(5, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(5), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(1), historyPageFilter.oldestOnPage); } /** * Test newerThan (page up) mid range nearer the oldest build in the list. */ @Test public void test_newerThan_near_oldest() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, 3L, null); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertTrue(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(5, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(8), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(4), historyPageFilter.oldestOnPage); } /** * Test newerThan (page up) mid range nearer the newest build in the list. This works a little different * in that it will put the 2 builds newer than newerThan on the page and then fill the remaining slots on the * page with builds equal to and older i.e. it return the newest/latest builds. */ @Test public void test_newerThan_near_newest() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, 8L, null); Iterable [MASK] = newRuns(1, 10); historyPageFilter.add( [MASK] ); Assert.assertFalse(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(5, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(10), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(6), historyPageFilter.oldestOnPage); } /** * Test newerThan (page up) mid range when there are queued builds that are new enough to * not show up. */ @Test public void test_newerThan_doesntIncludeQueuedItems() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, 5L, null); Iterable runs = newRuns(1, 10); var queueItems = newQueueItems(11, 12); historyPageFilter.add(runs, queueItems); Assert.assertTrue(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(0, historyPageFilter.queueItems.size()); Assert.assertEquals(5, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(10), historyPageFilter.runs.get(0).getEntryId()); Assert.assertEquals(HistoryPageEntry.getEntryId(10), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(6), historyPageFilter.oldestOnPage); } /** * Test that later items in the list that are not needed for display are not evaluated at all (for performance). */ @Test public void test_laterItemsNotEvaluated() throws IOException { HistoryPageFilter historyPageFilter = newPage(5, null, null); List [MASK] = new ArrayList<>(); for (ModelObject run : newRuns(6, 10)) { [MASK] .add(run); } for (int queueId = 5; queueId >= 1; queueId--) { [MASK] .add(new ExplodingMockRun(queueId)); } historyPageFilter.add( [MASK] ); Assert.assertFalse(historyPageFilter.hasUpPage); Assert.assertTrue(historyPageFilter.hasDownPage); Assert.assertEquals(5, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(10), historyPageFilter.newestOnPage); Assert.assertEquals(HistoryPageEntry.getEntryId(6), historyPageFilter.oldestOnPage); } @Test public void test_search_runs_by_build_number() throws IOException { //given HistoryPageFilter historyPageFilter = newPage(5, null, null); Iterable runs = newRuns(23, 24); var queueItems = newQueueItems(25, 26); //and historyPageFilter.setSearchString(""23""); //when historyPageFilter.add(runs, queueItems); //then Assert.assertEquals(1, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(23), historyPageFilter.runs.get(0).getEntryId()); } @Test @Issue(""JENKINS-42645"") public void should_be_case_insensitive_by_default() throws IOException { Iterable runs = Arrays.asList(new MockRun(2, Result.FAILURE), new MockRun(1, Result.SUCCESS)); assertOneMatchingBuildForGivenSearchStringAndRunItems(""failure"", runs); } @Test public void should_lower_case_search_string_in_case_insensitive_search() throws IOException { Iterable runs = Arrays.asList(new MockRun(2, Result.FAILURE), new MockRun(1, Result.SUCCESS)); assertOneMatchingBuildForGivenSearchStringAndRunItems(""FAILure"", runs); } @Test @Issue(""JENKINS-40718"") public void should_search_builds_by_build_variables() { Iterable runs = Arrays.asList( new MockBuild(2).withBuildVariables(Map.of(""env"", ""dummyEnv"")), new MockBuild(1).withBuildVariables(Map.of(""env"", ""otherEnv""))); assertOneMatchingBuildForGivenSearchStringAndRunItems(""dummyEnv"", runs); } @Test @Issue(""JENKINS-40718"") public void should_search_builds_by_build_params() throws IOException { Iterable runs = Arrays.asList( new MockBuild(2).withBuildParameters(Map.of(""env"", ""dummyEnv"")), new MockBuild(1).withBuildParameters(Map.of(""env"", ""otherEnv""))); assertOneMatchingBuildForGivenSearchStringAndRunItems(""dummyEnv"", runs); } @Test @Issue(""JENKINS-40718"") public void should_ignore_sensitive_parameters_in_search_builds_by_build_params() throws IOException { Iterable runs = Arrays.asList( new MockBuild(2).withBuildParameters(Map.of(""plainPassword"", ""pass1plain"")), new MockBuild(1).withSensitiveBuildParameters(""password"", ""pass1"")); assertOneMatchingBuildForGivenSearchStringAndRunItems(""pass1"", runs); } private void assertOneMatchingBuildForGivenSearchStringAndRunItems(String searchString, Iterable runs) { //given HistoryPageFilter historyPageFilter = newPage(5, null, null); //and historyPageFilter.setSearchString(searchString); //and var queueItems = newQueueItems(3, 4); //when historyPageFilter.add(runs, queueItems); //then Assert.assertEquals(1, historyPageFilter.runs.size()); Assert.assertEquals(HistoryPageEntry.getEntryId(2), historyPageFilter.runs.get(0).getEntryId()); } private List newQueueItems(long startId, long endId) { var items = new ArrayList(); for (long queueId = startId; queueId <= endId; queueId++) { items.add(new MockItem(queueId)); } return items; } private Iterable newRuns(long startId, long endId) throws IOException { // Runs should be in reverse order, newest first. List runs = new ArrayList<>(); for (long queueId = endId; queueId >= startId; queueId--) { runs.add(new MockRun(queueId)); } return runs; } private HistoryPageFilter newPage(int maxEntries, Long newerThan, Long olderThan) { HistoryPageFilter pageFilter = new HistoryPageFilter<>(maxEntries); if (newerThan != null) { pageFilter.setNewerThan(HistoryPageEntry.getEntryId(newerThan)); } else if (olderThan != null) { pageFilter.setOlderThan(HistoryPageEntry.getEntryId(olderThan)); } return pageFilter; } @SuppressWarnings(""unchecked"") private static class MockRun extends Run { private final long queueId; MockRun(long queueId) throws IOException { super(Mockito.mock(Job.class)); this.queueId = queueId; } MockRun(long queueId, Result result) throws IOException { this(queueId); this.result = result; } @Override public int compareTo(Run o) { return 0; } @Override public Result getResult() { return result; } @Override public boolean isBuilding() { return false; } @Override public long getQueueId() { return queueId; } @Override public int getNumber() { return (int) queueId; } @SuppressWarnings(""deprecation"") // avoid TransientActionFactory @Override public T getAction(Class type) { for (Action a : getActions()) { if (type.isInstance(a)) { return type.cast(a); } } return null; } } // A version of MockRun that will throw an exception if getQueueId or getNumber is called private static class ExplodingMockRun extends MockRun { ExplodingMockRun(long queueId) throws IOException { super(queueId); } @Override public long getQueueId() { Assert.fail(""Should not get called""); return super.getQueueId(); } @Override public int getNumber() { Assert.fail(""Should not get called""); return super.getNumber(); } } private static class MockBuild extends Build { private final int buildNumber; private Map buildVariables = Collections.emptyMap(); private MockBuild(int buildNumber) { super(Mockito.mock(FreeStyleProject.class), Mockito.mock(Calendar.class)); this.buildNumber = buildNumber; } @Override public int getNumber() { return buildNumber; } @Override public Map getBuildVariables() { return buildVariables; } MockBuild withBuildVariables(Map buildVariables) { this.buildVariables = buildVariables; return this; } MockBuild withBuildParameters(Map buildParametersAsMap) { addAction(new ParametersAction(buildPropertiesMapToParameterValues(buildParametersAsMap), buildParametersAsMap.keySet())); return this; } private List buildPropertiesMapToParameterValues(Map buildParametersAsMap) { return buildParametersAsMap.entrySet().stream() .map(parameter -> new StringParameterValue(parameter.getKey(), parameter.getValue())) .collect(Collectors.toList()); } MockBuild withSensitiveBuildParameters(String paramName, String paramValue) { addAction(new ParametersAction(List.of(createSensitiveStringParameterValue(paramName, paramValue)), List.of(paramName))); return this; } @SuppressWarnings(""deprecation"") // avoid TransientActionFactory @Override public T getAction(Class type) { for (Action a : getActions()) { if (type.isInstance(a)) { return type.cast(a); } } return null; } private StringParameterValue createSensitiveStringParameterValue(final String paramName, final String paramValue) { return new StringParameterValue(paramName, paramValue) { @Override public boolean isSensitive() { return true; } }; } } } ","itemList " "// Copyright 2006 The Bazel Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package net.starlark.java.syntax; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.ISO_8859_1; import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** A test case for {@link ParserInput}. */ @RunWith(JUnit4.class) public class ParserInputTest { @Test public void testFromLatin1() throws IOException { String [MASK] = ""éclair""; byte[] bytes = [MASK] .getBytes(ISO_8859_1); ParserInput input = ParserInput.fromLatin1(bytes, ""foo.txt""); assertThat(new String(input.getContent())).isEqualTo( [MASK] ); assertThat(input.getFile()).isEqualTo(""foo.txt""); } @Test public void testFromString() { String [MASK] = ""Content provided as a string.""; String pathName = ""/the/name/of/the/ [MASK] .txt""; ParserInput input = ParserInput.fromString( [MASK] , pathName); assertThat(new String(input.getContent())).isEqualTo( [MASK] ); assertThat(input.getFile()).isEqualTo(pathName); } @Test public void testFromCharArray() { String [MASK] = ""Content provided as a string.""; String pathName = ""/the/name/of/the/ [MASK] .txt""; char[] [MASK] Chars = [MASK] .toCharArray(); ParserInput input = ParserInput.fromCharArray( [MASK] Chars, pathName); assertThat(new String(input.getContent())).isEqualTo( [MASK] ); assertThat(input.getFile()).isEqualTo(pathName); } @Test public void testWillNotTryToReadInputFileIfContentProvidedAsString() { ParserInput.fromString(""Content provided as string."", ""/will/not/try/to/read""); } @Test public void testWillNotTryToReadInputFileIfContentProvidedAsChars() { char[] [MASK] = ""Content provided as char array."".toCharArray(); ParserInput.fromCharArray( [MASK] , ""/will/not/try/to/read""); } } ","content " "/* Copyright 2010 Google Inc. Copyright 2019,2020 OpenRefine contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS"" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.refine; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.refine. [MASK] s.Command; /** * Exposes protected methods of com.google.refine.RefineServlet as public for unit testing * */ public class RefineServletStub extends RefineServlet { private static File tempDir = null; // requirement of extending HttpServlet, not required for testing private static final long serialVersionUID = 1L; public void wrapService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.service(request, response); } public String wrapGetCommandName(HttpServletRequest request) { return super.getCommandKey(request); } @Override public File getTempDir() { if (tempDir == null) { try { Path tempDirPath = Files.createTempDirectory(""refine-test-dir""); tempDir = tempDirPath.toFile(); tempDir.deleteOnExit(); } catch (IOException e) { throw new RuntimeException(""Failed to create temp directory"", e); } } return tempDir; } // -------------------helper methods-------------- /** * Helper method for inserting a mock object * * @param [MASK] Name * @param [MASK] */ public void insertCommand(String [MASK] Name, Command [MASK] ) { registerOneCommand(""core/"" + [MASK] Name, [MASK] ); } /** * Helper method for clearing up after testing * * @param [MASK] Name */ public void removeCommand(String [MASK] Name) { unregisterCommand(""core/"" + [MASK] Name); } } ","command " "/* * Copyright 2013 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.util; import org.junit.jupiter.api.Test; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; public class HashedWheelTimerTest { @Test public void testScheduleTimeoutShouldNotRunBeforeDelay() throws InterruptedException { final Timer timer = new HashedWheelTimer(); final CountDownLatch [MASK] = new CountDownLatch(1); final Timeout timeout = timer.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { fail(""This should not have run""); [MASK] .countDown(); } }, 10, TimeUnit.SECONDS); assertFalse( [MASK] .await(3, TimeUnit.SECONDS)); assertFalse(timeout.isExpired(), ""timer should not expire""); timer.stop(); } @Test public void testScheduleTimeoutShouldRunAfterDelay() throws InterruptedException { final Timer timer = new HashedWheelTimer(); final CountDownLatch [MASK] = new CountDownLatch(1); final Timeout timeout = timer.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { [MASK] .countDown(); } }, 2, TimeUnit.SECONDS); assertTrue( [MASK] .await(3, TimeUnit.SECONDS)); assertTrue(timeout.isExpired(), ""timer should expire""); timer.stop(); } @Test @org.junit.jupiter.api.Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void testStopTimer() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(3); final Timer timerProcessed = new HashedWheelTimer(); for (int i = 0; i < 3; i ++) { timerProcessed.newTimeout(new TimerTask() { @Override public void run(final Timeout timeout) throws Exception { latch.countDown(); } }, 1, TimeUnit.MILLISECONDS); } latch.await(); assertEquals(0, timerProcessed.stop().size(), ""Number of unprocessed timeouts should be 0""); final Timer timerUnprocessed = new HashedWheelTimer(); for (int i = 0; i < 5; i ++) { timerUnprocessed.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { } }, 5, TimeUnit.SECONDS); } Thread.sleep(1000L); // sleep for a second assertFalse(timerUnprocessed.stop().isEmpty(), ""Number of unprocessed timeouts should be greater than 0""); } @Test @org.junit.jupiter.api.Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void testTimerShouldThrowExceptionAfterShutdownForNewTimeouts() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(3); final Timer timer = new HashedWheelTimer(); for (int i = 0; i < 3; i ++) { timer.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { latch.countDown(); } }, 1, TimeUnit.MILLISECONDS); } latch.await(); timer.stop(); try { timer.newTimeout(createNoOpTimerTask(), 1, TimeUnit.MILLISECONDS); fail(""Expected exception didn't occur.""); } catch (IllegalStateException ignored) { // expected } } @Test @org.junit.jupiter.api.Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testTimerOverflowWheelLength() throws InterruptedException { final HashedWheelTimer timer = new HashedWheelTimer( Executors.defaultThreadFactory(), 100, TimeUnit.MILLISECONDS, 32); final CountDownLatch latch = new CountDownLatch(3); timer.newTimeout(new TimerTask() { @Override public void run(final Timeout timeout) throws Exception { timer.newTimeout(this, 100, TimeUnit.MILLISECONDS); latch.countDown(); } }, 100, TimeUnit.MILLISECONDS); latch.await(); assertFalse(timer.stop().isEmpty()); } @Test public void testExecutionOnTime() throws InterruptedException { int tickDuration = 200; int timeout = 125; int maxTimeout = 2 * (tickDuration + timeout); final HashedWheelTimer timer = new HashedWheelTimer(tickDuration, TimeUnit.MILLISECONDS); final BlockingQueue queue = new LinkedBlockingQueue(); int scheduledTasks = 100000; for (int i = 0; i < scheduledTasks; i++) { final long start = System.nanoTime(); timer.newTimeout(new TimerTask() { @Override public void run(final Timeout timeout) throws Exception { queue.add(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)); } }, timeout, TimeUnit.MILLISECONDS); } for (int i = 0; i < scheduledTasks; i++) { long delay = queue.take(); assertTrue(delay >= timeout && delay < maxTimeout, ""Timeout + "" + scheduledTasks + "" delay "" + delay + "" must be "" + timeout + "" < "" + maxTimeout); } timer.stop(); } @Test public void testExecutionOnTaskExecutor() throws InterruptedException { int timeout = 10; final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch timeoutLatch = new CountDownLatch(1); Executor executor = new Executor() { @Override public void execute(Runnable command) { try { command.run(); } finally { latch.countDown(); } } }; final HashedWheelTimer timer = new HashedWheelTimer(Executors.defaultThreadFactory(), 100, TimeUnit.MILLISECONDS, 32, true, 2, executor); timer.newTimeout(new TimerTask() { @Override public void run(final Timeout timeout) throws Exception { timeoutLatch.countDown(); } }, timeout, TimeUnit.MILLISECONDS); latch.await(); timeoutLatch.await(); timer.stop(); } @Test public void testRejectedExecutionExceptionWhenTooManyTimeoutsAreAddedBackToBack() { HashedWheelTimer timer = new HashedWheelTimer(Executors.defaultThreadFactory(), 100, TimeUnit.MILLISECONDS, 32, true, 2); timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS); timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS); try { timer.newTimeout(createNoOpTimerTask(), 1, TimeUnit.MILLISECONDS); fail(""Timer allowed adding 3 timeouts when maxPendingTimeouts was 2""); } catch (RejectedExecutionException e) { // Expected } finally { timer.stop(); } } @Test public void testNewTimeoutShouldStopThrowingRejectedExecutionExceptionWhenExistingTimeoutIsCancelled() throws InterruptedException { final int tickDurationMs = 100; final HashedWheelTimer timer = new HashedWheelTimer(Executors.defaultThreadFactory(), tickDurationMs, TimeUnit.MILLISECONDS, 32, true, 2); timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS); Timeout timeoutToCancel = timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS); assertTrue(timeoutToCancel.cancel()); Thread.sleep(tickDurationMs * 5); final CountDownLatch secondLatch = new CountDownLatch(1); timer.newTimeout(createCountDownLatchTimerTask(secondLatch), 90, TimeUnit.MILLISECONDS); secondLatch.await(); timer.stop(); } @Test @org.junit.jupiter.api.Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void testNewTimeoutShouldStopThrowingRejectedExecutionExceptionWhenExistingTimeoutIsExecuted() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final HashedWheelTimer timer = new HashedWheelTimer(Executors.defaultThreadFactory(), 25, TimeUnit.MILLISECONDS, 4, true, 2); timer.newTimeout(createNoOpTimerTask(), 5, TimeUnit.SECONDS); timer.newTimeout(createCountDownLatchTimerTask(latch), 90, TimeUnit.MILLISECONDS); latch.await(); final CountDownLatch secondLatch = new CountDownLatch(1); timer.newTimeout(createCountDownLatchTimerTask(secondLatch), 90, TimeUnit.MILLISECONDS); secondLatch.await(); timer.stop(); } @Test() public void reportPendingTimeouts() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final HashedWheelTimer timer = new HashedWheelTimer(); final Timeout t1 = timer.newTimeout(createNoOpTimerTask(), 100, TimeUnit.MINUTES); final Timeout t2 = timer.newTimeout(createNoOpTimerTask(), 100, TimeUnit.MINUTES); timer.newTimeout(createCountDownLatchTimerTask(latch), 90, TimeUnit.MILLISECONDS); assertEquals(3, timer.pendingTimeouts()); t1.cancel(); t2.cancel(); latch.await(); assertEquals(0, timer.pendingTimeouts()); timer.stop(); } @Test public void testOverflow() throws InterruptedException { final HashedWheelTimer timer = new HashedWheelTimer(); final CountDownLatch latch = new CountDownLatch(1); Timeout timeout = timer.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) { latch.countDown(); } }, Long.MAX_VALUE, TimeUnit.MILLISECONDS); assertFalse(latch.await(1, TimeUnit.SECONDS)); timeout.cancel(); timer.stop(); } private static TimerTask createNoOpTimerTask() { return new TimerTask() { @Override public void run(final Timeout timeout) throws Exception { } }; } private static TimerTask createCountDownLatchTimerTask(final CountDownLatch latch) { return new TimerTask() { @Override public void run(final Timeout timeout) throws Exception { latch.countDown(); } }; } } ","barrier " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.optimize.peephole; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import java.util.*; /** * This ClassVisitor disambiguates line numbers, in the classes that it * visits. It shifts line numbers that originate from different classes * (e.g. due to method inlining or class merging) to blocks that don't * overlap with the main line numbers and with each other. The line numbers * then uniquely identify the inlined and merged code in the classes. * * @author Eric Lafortune */ public class LineNumberLinearizer extends SimplifiedVisitor implements ClassVisitor, MemberVisitor, AttributeVisitor, LineNumberInfoVisitor { private static final boolean DEBUG = false; public static final int SHIFT_ROUNDING = 1000; private static final int SHIFT_ROUNDING_LIMIT = 50000; private Stack enclosingLineNumbers = new Stack(); private LineNumberInfo previousLineNumberInfo; private int highestUsedLineNumber; private int currentLineNumberShift; // Implementations for ClassVisitor. public void visitProgramClass(ProgramClass programClass) { // Find the highest line number in the entire class. LineNumberRangeFinder [MASK] = new LineNumberRangeFinder(); programClass.methodsAccept(new AllAttributeVisitor(true, new AllLineNumberInfoVisitor( [MASK] ))); // Are there any inlined line numbers? if ( [MASK] .hasSource()) { // Remember the minimum initial shift. highestUsedLineNumber = [MASK] .getHighestLineNumber(); // Shift the inlined line numbers. programClass.methodsAccept(this); } } // Implementations for MemberVisitor. public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { programMethod.attributesAccept(programClass, this); } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.attributesAccept(clazz, method, this); } public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) { if (DEBUG) { System.out.println(""LineNumberLinearizer [""+clazz.getName()+"".""+method.getName(clazz)+method.getDescriptor(clazz)+""]:""); } enclosingLineNumbers.clear(); previousLineNumberInfo = null; // Process all line numbers. lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this); } // Implementations for LineNumberInfoVisitor. public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) { String source = lineNumberInfo.getSource(); if (DEBUG) { System.out.print("" ["" + lineNumberInfo.u2startPC + ""] line "" + lineNumberInfo.u2lineNumber + (source == null ? """" : "" ["" + source + ""]"")); } // Is it an inlined line number? if (source != null) { ExtendedLineNumberInfo extendedLineNumberInfo = (ExtendedLineNumberInfo)lineNumberInfo; int lineNumber = extendedLineNumberInfo.u2lineNumber; // Are we entering or exiting a new inlined block? if (previousLineNumberInfo == null || previousLineNumberInfo.getSource() != source) { // Are we entering a new inlined block? if (lineNumber != MethodInliner.INLINED_METHOD_END_LINE_NUMBER) { // Remember information about the inlined block. enclosingLineNumbers.push(previousLineNumberInfo != null ? new MyLineNumberBlock(currentLineNumberShift, previousLineNumberInfo.u2lineNumber, previousLineNumberInfo.getSource()) : new MyLineNumberBlock(0, 0, null)); // Parse the end line number from the source string, // so we know how large a block this will be. int separatorIndex1 = source.indexOf(':'); int separatorIndex2 = source.indexOf(':', separatorIndex1 + 1); int startLineNumber = Integer.parseInt(source.substring(separatorIndex1 + 1, separatorIndex2)); int endLineNumber = Integer.parseInt(source.substring(separatorIndex2 + 1)); // Start shifting, if necessary, so the block ends up beyond // the highest used line number. We're striving for rounded // shifts, unless we've reached a given limit, to avoid // running out of line numbers too quickly. currentLineNumberShift = highestUsedLineNumber > SHIFT_ROUNDING_LIMIT ? highestUsedLineNumber - startLineNumber + 1 : startLineNumber > highestUsedLineNumber ? 0 : (highestUsedLineNumber - startLineNumber + SHIFT_ROUNDING) / SHIFT_ROUNDING * SHIFT_ROUNDING; highestUsedLineNumber = endLineNumber + currentLineNumberShift; if (DEBUG) { System.out.print("" (enter with shift ""+currentLineNumberShift+"")""); } // Apply the shift. lineNumberInfo.u2lineNumber += currentLineNumberShift; } // TODO: There appear to be cases where the stack is empty at this point, so we've added a check. else if (enclosingLineNumbers.isEmpty()) { if (DEBUG) { System.err.println(""Problem linearizing line numbers for optimized code (""+clazz.getName()+"".""+method.getName(clazz)+"")""); } } // Are we exiting an inlined block? else { // Pop information about the enclosing line number. MyLineNumberBlock lineNumberBlock = (MyLineNumberBlock)enclosingLineNumbers.pop(); // Set this end of the block to the line at which it was // inlined. extendedLineNumberInfo.u2lineNumber = lineNumberBlock.enclosingLineNumber; extendedLineNumberInfo.source = lineNumberBlock.enclosingSource; // Reset the shift to the shift of the block. currentLineNumberShift = lineNumberBlock.lineNumberShift; if (DEBUG) { System.out.print("" (exit to shift ""+currentLineNumberShift+"")""); } } } else { if (DEBUG) { System.out.print("" (apply shift ""+currentLineNumberShift+"")""); } // Apply the shift. lineNumberInfo.u2lineNumber += currentLineNumberShift; } } previousLineNumberInfo = lineNumberInfo; if (DEBUG) { System.out.println("" -> line "" + lineNumberInfo.u2lineNumber); } } /** * This class represents a block of line numbers that originates from the * same inlined method. */ private static class MyLineNumberBlock { public final int lineNumberShift; public final int enclosingLineNumber; public final String enclosingSource; public MyLineNumberBlock(int lineNumberShift, int enclosingLineNumber, String enclosingSource) { this.lineNumberShift = lineNumberShift; this.enclosingLineNumber = enclosingLineNumber; this.enclosingSource = enclosingSource; } } } ","lineNumberRangeFinder " "/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.compression; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.EncoderException; import java.util.concurrent.TimeUnit; import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4Factory; import net.jpountz.xxhash.XXHashFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.Executable; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.InputStream; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.Checksum; import static io.netty.handler.codec.compression.Lz4Constants.DEFAULT_SEED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.not; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; public class Lz4FrameEncoderTest extends AbstractEncoderTest { /** * For the purposes of this test, if we pass this (very small) size of buffer into * {@link Lz4FrameEncoder#allocateBuffer(ChannelHandlerContext, ByteBuf, boolean)}, we should get back * an empty buffer. */ private static final int NONALLOCATABLE_SIZE = 1; @Mock private ChannelHandlerContext ctx; /** * A {@link ByteBuf} for mocking purposes, largely because it's difficult to allocate to huge buffers. */ @Mock private ByteBuf buffer; @BeforeEach public void setup() { MockitoAnnotations.initMocks(this); when(ctx.alloc()).thenReturn(ByteBufAllocator.DEFAULT); } @Override protected EmbeddedChannel createChannel() { return new EmbeddedChannel(new Lz4FrameEncoder()); } @Override protected ByteBuf decompress(ByteBuf compressed, int originalLength) throws Exception { InputStream is = new ByteBufInputStream(compressed, true); LZ4BlockInputStream lz4Is = null; byte[] decompressed = new byte[originalLength]; try { lz4Is = new LZ4BlockInputStream(is); int remaining = originalLength; while (remaining > 0) { int read = lz4Is.read(decompressed, originalLength - remaining, remaining); if (read > 0) { remaining -= read; } else { break; } } assertEquals(-1, lz4Is.read()); } finally { if (lz4Is != null) { lz4Is.close(); } else { is.close(); } } return Unpooled.wrappedBuffer(decompressed); } @Test public void testAllocateDirectBuffer() { final int blockSize = 100; testAllocateBuffer(blockSize, blockSize - 13, true); testAllocateBuffer(blockSize, blockSize * 5, true); testAllocateBuffer(blockSize, NONALLOCATABLE_SIZE, true); } @Test public void testAllocateHeapBuffer() { final int blockSize = 100; testAllocateBuffer(blockSize, blockSize - 13, false); testAllocateBuffer(blockSize, blockSize * 5, false); testAllocateBuffer(blockSize, NONALLOCATABLE_SIZE, false); } private void testAllocateBuffer(int blockSize, int bufSize, boolean preferDirect) { // allocate the input buffer to an arbitrary size less than the blockSize ByteBuf in = ByteBufAllocator.DEFAULT.buffer(bufSize, bufSize); in.writerIndex(in.capacity()); ByteBuf out = null; try { Lz4FrameEncoder encoder = newEncoder(blockSize, Lz4FrameEncoder.DEFAULT_MAX_ENCODE_SIZE); out = encoder.allocateBuffer(ctx, in, preferDirect); assertNotNull(out); if (NONALLOCATABLE_SIZE == bufSize) { assertFalse(out.isWritable()); } else { assertTrue(out.writableBytes() > 0); if (!preferDirect) { // Only check if preferDirect is not true as if a direct buffer is returned or not depends on // if sun.misc.Unsafe is present. assertFalse(out.isDirect()); } } } finally { in.release(); if (out != null) { out.release(); } } } @Test public void testAllocateDirectBufferExceedMaxEncodeSize() { final int maxEncodeSize = 1024; final Lz4FrameEncoder encoder = newEncoder(Lz4Constants.DEFAULT_BLOCK_SIZE, maxEncodeSize); int inputBufferSize = maxEncodeSize * 10; final ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(inputBufferSize, inputBufferSize); try { buf.writerIndex(inputBufferSize); assertThrows(EncoderException.class, new Executable() { @Override public void execute() { encoder.allocateBuffer(ctx, buf, false); } }); } finally { buf.release(); } } private Lz4FrameEncoder newEncoder(int blockSize, int maxEncodeSize) { Checksum checksum = XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum(); Lz4FrameEncoder encoder = new Lz4FrameEncoder(LZ4Factory.fastestInstance(), true, blockSize, checksum, maxEncodeSize); encoder.handlerAdded(ctx); return encoder; } /** * This test might be a invasive in terms of knowing what happens inside * {@link Lz4FrameEncoder#allocateBuffer(ChannelHandlerContext, ByteBuf, boolean)}, but this is safest way * of testing the overflow conditions as allocating the huge buffers fails in many CI environments. */ @Test public void testAllocateOnHeapBufferOverflowsOutputSize() { final int maxEncodeSize = Integer.MAX_VALUE; final Lz4FrameEncoder encoder = newEncoder(Lz4Constants.DEFAULT_BLOCK_SIZE, maxEncodeSize); when(buffer.readableBytes()).thenReturn(maxEncodeSize); buffer.writerIndex(maxEncodeSize); assertThrows(EncoderException.class, new Executable() { @Override public void execute() { encoder.allocateBuffer(ctx, buffer, false); } }); } @Test public void testFlush() { Lz4FrameEncoder encoder = new Lz4FrameEncoder(); EmbeddedChannel channel = new EmbeddedChannel(encoder); int size = 27; ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(size, size); buf.writerIndex(size); assertEquals(0, encoder.getBackingBuffer().readableBytes()); channel.write(buf); assertTrue(channel.outboundMessages().isEmpty()); assertEquals(size, encoder.getBackingBuffer().readableBytes()); channel.flush(); assertTrue(channel.finish()); assertTrue(channel.releaseOutbound()); assertFalse(channel.releaseInbound()); } @Test public void testAllocatingAroundBlockSize() { int blockSize = 100; Lz4FrameEncoder encoder = newEncoder(blockSize, Lz4FrameEncoder.DEFAULT_MAX_ENCODE_SIZE); EmbeddedChannel channel = new EmbeddedChannel(encoder); int size = blockSize - 1; ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(size, size); buf.writerIndex(size); assertEquals(0, encoder.getBackingBuffer().readableBytes()); channel.write(buf); assertEquals(size, encoder.getBackingBuffer().readableBytes()); int nextSize = size - 1; buf = ByteBufAllocator.DEFAULT.buffer(nextSize, nextSize); buf.writerIndex(nextSize); channel.write(buf); assertEquals(size + nextSize - blockSize, encoder.getBackingBuffer().readableBytes()); channel.flush(); assertEquals(0, encoder.getBackingBuffer().readableBytes()); assertTrue(channel.finish()); assertTrue(channel.releaseOutbound()); assertFalse(channel.releaseInbound()); } @Test @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void writingAfterClosedChannelDoesNotNPE() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup(2); Channel serverChannel = null; Channel clientChannel = null; final CountDownLatch latch = new CountDownLatch(1); final AtomicReference [MASK] Ref = new AtomicReference(); try { ServerBootstrap sb = new ServerBootstrap(); sb.group(group); sb.channel(NioServerSocketChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { } }); Bootstrap bs = new Bootstrap(); bs.group(group); bs.channel(NioSocketChannel.class); bs.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new Lz4FrameEncoder()); } }); serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); clientChannel = bs.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); final Channel finalClientChannel = clientChannel; clientChannel.eventLoop().execute(new Runnable() { @Override public void run() { finalClientChannel.close(); final int size = 27; ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(size, size); finalClientChannel.writeAndFlush(buf.writerIndex(buf.writerIndex() + size)) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { try { [MASK] Ref.set(future.cause()); } finally { latch.countDown(); } } }); } }); latch.await(); Throwable [MASK] = [MASK] Ref.get(); assertNotNull( [MASK] ); Throwable [MASK] Cause = [MASK] .getCause(); if ( [MASK] Cause != null) { assertThat( [MASK] Cause, is(not(instanceOf(NullPointerException.class)))); } } finally { if (serverChannel != null) { serverChannel.close(); } if (clientChannel != null) { clientChannel.close(); } group.shutdownGracefully(); } } } ","writeFailCause " "/* * The MIT License * * Copyright (c) 2004-2010, Sun Microsystems, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the ""Software""), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.cli.declarative; import static java.util.logging.Level.SEVERE; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.AbortException; import hudson.Extension; import hudson.ExtensionComponent; import hudson.ExtensionFinder; import hudson.Functions; import hudson.Util; import hudson.cli.CLICommand; import hudson.cli.CloneableCLICommand; import hudson.model.Hudson; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.Stack; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import [MASK] .ExtensionComponentSet; import [MASK] .ExtensionRefreshException; import [MASK] .model.Jenkins; import org.jvnet.hudson.annotation_indexer.Index; import org.jvnet.localizer.ResourceBundleHolder; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** * Discover {@link CLIMethod}s and register them as {@link CLICommand} implementations. * * @author Kohsuke Kawaguchi */ @Extension public class CLIRegisterer extends ExtensionFinder { @Override public ExtensionComponentSet refresh() throws ExtensionRefreshException { // TODO: this is not complex. just bit tedious. return ExtensionComponentSet.EMPTY; } @Override public Collection> find(Class type, Hudson [MASK] ) { if (type == CLICommand.class) return (List) discover( [MASK] ); else return Collections.emptyList(); } /** * Finds a resolved method annotated with {@link CLIResolver}. */ private Method findResolver(Class type) throws IOException { List resolvers = Util.filter(Index.list(CLIResolver.class, Jenkins.get().getPluginManager().uberClassLoader), Method.class); for ( ; type != null; type = type.getSuperclass()) for (Method m : resolvers) if (m.getReturnType() == type) return m; return null; } private List> discover(@NonNull final Jenkins [MASK] ) { LOGGER.fine(""Listing up @CLIMethod""); List> r = new ArrayList<>(); try { for (final Method m : Util.filter(Index.list(CLIMethod.class, [MASK] .getPluginManager().uberClassLoader), Method.class)) { try { // command name final String name = m.getAnnotation(CLIMethod.class).name(); final ResourceBundleHolder res = loadMessageBundle(m); res.format(""CLI."" + name + "".shortDescription""); // make sure we have the resource, to fail early r.add(new ExtensionComponent<>(new CloneableCLICommand() { @Override public String getName() { return name; } @Override public String getShortDescription() { // format by using the right locale return res.format(""CLI."" + name + "".shortDescription""); } @Override protected CmdLineParser getCmdLineParser() { return bindMethod(new ArrayList<>()); } private CmdLineParser bindMethod(List binders) { registerOptionHandlers(); CmdLineParser parser = new CmdLineParser(null); // build up the call sequence Stack chains = new Stack<>(); Method method = m; while (true) { chains.push(method); if (Modifier.isStatic(method.getModifiers())) break; // the chain is complete. // the method in question is an instance method, so we need to resolve the instance by using another resolver Class type = method.getDeclaringClass(); try { method = findResolver(type); } catch (IOException ex) { throw new RuntimeException(""Unable to find the resolver method annotated with @CLIResolver for "" + type, ex); } if (method == null) { throw new RuntimeException(""Unable to find the resolver method annotated with @CLIResolver for "" + type); } } while (!chains.isEmpty()) binders.add(new MethodBinder(chains.pop(), this, parser)); return parser; } /** * Envelope an annotated CLI command * * @param args * Arguments to the sub command. For example, if the CLI is invoked like ""java -jar cli.jar foo bar zot"", * then ""foo"" is the sub-command and the argument list is [""bar"",""zot""]. * @param locale * Locale of the client (which can be different from that of the server.) Good behaving command implementation * would use this locale for formatting messages. * @param stdin * Connected to the stdin of the CLI client. * @param stdout * Connected to the stdout of the CLI client. * @param stderr * Connected to the stderr of the CLI client. * @return * Exit code from the CLI command execution * *

* Jenkins standard exit codes from CLI: * 0 means everything went well. * 1 means further unspecified exception is thrown while performing the command. * 2 means CmdLineException is thrown while performing the command. * 3 means IllegalArgumentException is thrown while performing the command. * 4 mean IllegalStateException is thrown while performing the command. * 5 means AbortException is thrown while performing the command. * 6 means AccessDeniedException is thrown while performing the command. * 7 means BadCredentialsException is thrown while performing the command. * 8-15 are reserved for future usage * 16+ mean a custom CLI exit error code (meaning defined by the CLI command itself) * *

* Note: For details - see JENKINS-32273 */ @Override public int main(List args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) { this.stdout = stdout; this.stderr = stderr; this.locale = locale; List binders = new ArrayList<>(); CmdLineParser parser = bindMethod(binders); try { // TODO this could probably use ACL.as; why is it calling SecurityContext.setAuthentication rather than SecurityContextHolder.setContext? SecurityContext sc = SecurityContextHolder.getContext(); Authentication old = sc.getAuthentication(); try { // fill up all the binders parser.parseArgument(args); Authentication auth = getTransportAuthentication2(); sc.setAuthentication(auth); // run the CLI with the right credential [MASK] .checkPermission(Jenkins.READ); // resolve them Object instance = null; for (MethodBinder binder : binders) instance = binder.call(instance); if (instance instanceof Integer) return (Integer) instance; else return 0; } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof Exception) throw (Exception) t; throw e; } finally { sc.setAuthentication(old); // restore } } catch (CmdLineException e) { printError(e.getMessage()); printUsage(stderr, parser); return 2; } catch (IllegalStateException e) { printError(e.getMessage()); return 4; } catch (IllegalArgumentException e) { printError(e.getMessage()); return 3; } catch (AbortException e) { printError(e.getMessage()); return 5; } catch (AccessDeniedException e) { printError(e.getMessage()); return 6; } catch (BadCredentialsException e) { // to the caller, we can't reveal whether the user didn't exist or the password didn't match. // do that to the server log instead String id = UUID.randomUUID().toString(); logAndPrintError(e, ""Bad Credentials. Search the server log for "" + id + "" for more details."", ""CLI login attempt failed: "" + id, Level.INFO); return 7; } catch (Throwable e) { final String errorMsg = ""Unexpected exception occurred while performing "" + getName() + "" command.""; logAndPrintError(e, errorMsg, errorMsg, Level.WARNING); Functions.printStackTrace(e, stderr); return 1; } } private void printError(String errorMessage) { this.stderr.println(); this.stderr.println(""ERROR: "" + errorMessage); } private void logAndPrintError(Throwable e, String errorMessage, String logMessage, Level logLevel) { LOGGER.log(logLevel, logMessage, e); printError(errorMessage); } @Override protected int run() throws Exception { throw new UnsupportedOperationException(); } })); } catch (ClassNotFoundException | MissingResourceException e) { LOGGER.log(SEVERE, ""Failed to process @CLIMethod: "" + m, e); } } } catch (IOException e) { LOGGER.log(SEVERE, ""Failed to discover @CLIMethod"", e); } return r; } /** * Locates the {@link ResourceBundleHolder} for this CLI method. */ private ResourceBundleHolder loadMessageBundle(Method m) throws ClassNotFoundException { Class c = m.getDeclaringClass(); Class msg = c.getClassLoader().loadClass(c.getName().substring(0, c.getName().lastIndexOf(""."")) + "".Messages""); return ResourceBundleHolder.get(msg); } private static final Logger LOGGER = Logger.getLogger(CLIRegisterer.class.getName()); } ","jenkins " "package jadx.plugins.input.dex.smali; import java.util.HashMap; import java.util.Map; import org.jetbrains.annotations.NotNull; import jadx.api.plugins.input.insns.InsnData; import jadx.plugins.input.dex.insns.DexOpcodes; public class SmaliInsnFormat { private static SmaliInsnFormat instance; public static synchronized SmaliInsnFormat getInstance() { SmaliInsnFormat instance = SmaliInsnFormat.instance; if (instance == null) { instance = new SmaliInsnFormat(); SmaliInsnFormat.instance = instance; } return instance; } private final Map formatters; public SmaliInsnFormat() { formatters = registerFormatters(); } private Map registerFormatters() { Map map = new HashMap<>(); map.put(DexOpcodes.NOP, fi -> fi.getCodeWriter().add(""nop"")); map.put(DexOpcodes.SGET_OBJECT, staticFieldInsn(""sget-object"")); map.put(DexOpcodes.SPUT_BOOLEAN, staticFieldInsn(""sput-boolean"")); map.put(DexOpcodes.CONST, constInsn(""const"")); map.put(DexOpcodes.CONST_HIGH16, constInsn(""const/high16"")); map.put(DexOpcodes.CONST_STRING, stringInsn(""const-string"")); map.put(DexOpcodes.INVOKE_VIRTUAL, invokeInsn(""invoke-virtual"")); map.put(DexOpcodes.INVOKE_DIRECT, invokeInsn(""invoke-direct"")); map.put(DexOpcodes.INVOKE_SUPER, invokeInsn(""invoke-super"")); map.put(DexOpcodes.INVOKE_STATIC, invokeInsn(""invoke-static"")); map.put(DexOpcodes.MOVE_RESULT, oneArgsInsn(""move-result"")); map.put(DexOpcodes.RETURN_VOID, noArgsInsn(""return-void"")); map.put(DexOpcodes.GOTO, gotoInsn(""goto"")); map.put(DexOpcodes.GOTO_16, gotoInsn(""goto-16"")); map.put(DexOpcodes.MOVE, simpleInsn(""move"")); // TODO: complete list return map; } private InsnFormatter simpleInsn(String name) { return fi -> { SmaliCodeWriter code = fi.getCodeWriter(); code.add(name); InsnData insn = fi.getInsn(); int regsCount = insn.getRegsCount(); for (int i = 0; i < regsCount; i++) { if (i == 0) { code.add(' '); } else { code.add("", ""); } code.add(regAt(fi, i)); } }; } private InsnFormatter gotoInsn(String name) { return fi -> fi.getCodeWriter().add(name).add("" :goto"").add(Integer.toHexString(fi.getInsn().getTarget())); } @NotNull private InsnFormatter staticFieldInsn(String name) { return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add("", "").add(field(fi)); } @NotNull private InsnFormatter constInsn(String name) { return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add("", "").add(literal(fi)); } @NotNull private InsnFormatter stringInsn(String name) { return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add("", "").add(str(fi)); } @NotNull private InsnFormatter invokeInsn(String name) { return fi -> { SmaliCodeWriter code = fi.getCodeWriter(); code.add(name).add(' '); regsList(code, fi.getInsn()); code.add("", "").add(method(fi)); }; } private InsnFormatter oneArgsInsn(String name) { return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)); } private InsnFormatter noArgsInsn(String name) { return (fi) -> fi.getCodeWriter().add(name); } private String literal(InsnFormatterInfo fi) { return ""0x"" + Long.toHexString(fi.getInsn().getLiteral()); } private String str(InsnFormatterInfo fi) { return ""\"""" + fi.getInsn().getIndexAsString() + ""\""""; } private String field(InsnFormatterInfo fi) { return fi.getInsn().getIndexAsField().toString(); } private String method(InsnFormatterInfo fi) { return fi.getInsn().getIndexAsMethod().toString(); } private void regsList(SmaliCodeWriter code, InsnData insn) { int argsCount = insn.getRegsCount(); code.add('{'); for (int i = 0; i < argsCount; i++) { if (i != 0) { code.add("", ""); } code.add(""v"").add(insn.getReg(i)); } code.add('}'); } private String regAt(InsnFormatterInfo fi, int argNum) { return ""v"" + fi.getInsn().getReg(argNum); } public void format(InsnFormatterInfo formatInfo) { InsnData insn = formatInfo.getInsn(); insn.decode(); int rawOpcodeUnit = insn.getRawOpcodeUnit(); int [MASK] = rawOpcodeUnit & 0xFF; InsnFormatter insnFormatter = formatters.get( [MASK] ); if (insnFormatter != null) { insnFormatter.format(formatInfo); } else { formatInfo.getCodeWriter().add(""# "").add(insn.getOpcode()).add("" (?0x"").add(Integer.toHexString(rawOpcodeUnit)).add(')'); } } public String format(InsnData insn) { InsnFormatterInfo formatInfo = new InsnFormatterInfo(new SmaliCodeWriter(), insn); format(formatInfo); return formatInfo.getCodeWriter().getCode(); } } ","opcode " "/* * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.text; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.text.webvtt.WebvttDecoder; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; /** Tests for {@link SubtitleExtractor}. */ @RunWith(AndroidJUnit4.class) public class SubtitleExtractorTest { private static final String TEST_DATA = ""WEBVTT\n"" + ""\n"" + ""00:00.000 --> 00:01.234\n"" + ""This is the first subtitle.\n"" + ""\n"" + ""00:02.345 --> 00:03.456\n"" + ""This is the second subtitle.\n"" + ""\n"" + ""00:02.600 --> 00:04.567\n"" + ""This is the third subtitle.\n""; @Test public void extractor_outputsCues() throws Exception { CueDecoder decoder = new CueDecoder(); FakeExtractorOutput output = new FakeExtractorOutput(); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(Util.getUtf8Bytes(TEST_DATA)) .setSimulatePartialReads(true) .build(); SubtitleExtractor extractor = new SubtitleExtractor( new WebvttDecoder(), new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build()); extractor.init(output); while (extractor.read(input, null) != Extractor.RESULT_END_OF_INPUT) {} FakeTrackOutput [MASK] = output. [MASK] s.get(0); assertThat( [MASK] .lastFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_EXOPLAYER_CUES); assertThat( [MASK] .lastFormat.codecs).isEqualTo(MimeTypes.TEXT_VTT); assertThat( [MASK] .getSampleCount()).isEqualTo(6); // Check sample timestamps. assertThat( [MASK] .getSampleTimeUs(0)).isEqualTo(0L); assertThat( [MASK] .getSampleTimeUs(1)).isEqualTo(1_234_000L); assertThat( [MASK] .getSampleTimeUs(2)).isEqualTo(2_345_000L); assertThat( [MASK] .getSampleTimeUs(3)).isEqualTo(2_600_000L); assertThat( [MASK] .getSampleTimeUs(4)).isEqualTo(3_456_000L); assertThat( [MASK] .getSampleTimeUs(5)).isEqualTo(4_567_000L); // Check sample content. List cues0 = decoder.decode( [MASK] .getSampleData(0)); assertThat(cues0).hasSize(1); assertThat(cues0.get(0).text.toString()).isEqualTo(""This is the first subtitle.""); List cues1 = decoder.decode( [MASK] .getSampleData(1)); assertThat(cues1).isEmpty(); List cues2 = decoder.decode( [MASK] .getSampleData(2)); assertThat(cues2).hasSize(1); assertThat(cues2.get(0).text.toString()).isEqualTo(""This is the second subtitle.""); List cues3 = decoder.decode( [MASK] .getSampleData(3)); assertThat(cues3).hasSize(2); assertThat(cues3.get(0).text.toString()).isEqualTo(""This is the second subtitle.""); assertThat(cues3.get(1).text.toString()).isEqualTo(""This is the third subtitle.""); List cues4 = decoder.decode( [MASK] .getSampleData(4)); assertThat(cues4).hasSize(1); assertThat(cues4.get(0).text.toString()).isEqualTo(""This is the third subtitle.""); List cues5 = decoder.decode( [MASK] .getSampleData(5)); assertThat(cues5).isEmpty(); } @Test public void extractor_seekAfterExtracting_outputsCues() throws Exception { CueDecoder decoder = new CueDecoder(); FakeExtractorOutput output = new FakeExtractorOutput(); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(Util.getUtf8Bytes(TEST_DATA)) .setSimulatePartialReads(true) .build(); SubtitleExtractor extractor = new SubtitleExtractor( new WebvttDecoder(), new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build()); extractor.init(output); FakeTrackOutput [MASK] = output. [MASK] s.get(0); while (extractor.read(input, null) != Extractor.RESULT_END_OF_INPUT) {} extractor.seek((int) output.seekMap.getSeekPoints(2_445_000L).first.position, 2_445_000L); input.setPosition((int) output.seekMap.getSeekPoints(2_445_000L).first.position); [MASK] .clear(); while (extractor.read(input, null) != Extractor.RESULT_END_OF_INPUT) {} assertThat( [MASK] .lastFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_EXOPLAYER_CUES); assertThat( [MASK] .lastFormat.codecs).isEqualTo(MimeTypes.TEXT_VTT); assertThat( [MASK] .getSampleCount()).isEqualTo(4); // Check sample timestamps. assertThat( [MASK] .getSampleTimeUs(0)).isEqualTo(2_345_000L); assertThat( [MASK] .getSampleTimeUs(1)).isEqualTo(2_600_000L); assertThat( [MASK] .getSampleTimeUs(2)).isEqualTo(3_456_000L); assertThat( [MASK] .getSampleTimeUs(3)).isEqualTo(4_567_000L); // Check sample content. List cues0 = decoder.decode( [MASK] .getSampleData(0)); assertThat(cues0).hasSize(1); assertThat(cues0.get(0).text.toString()).isEqualTo(""This is the second subtitle.""); List cues1 = decoder.decode( [MASK] .getSampleData(1)); assertThat(cues1).hasSize(2); assertThat(cues1.get(0).text.toString()).isEqualTo(""This is the second subtitle.""); assertThat(cues1.get(1).text.toString()).isEqualTo(""This is the third subtitle.""); List cues2 = decoder.decode( [MASK] .getSampleData(2)); assertThat(cues2).hasSize(1); assertThat(cues2.get(0).text.toString()).isEqualTo(""This is the third subtitle.""); List cues3 = decoder.decode( [MASK] .getSampleData(3)); assertThat(cues3).isEmpty(); } @Test public void extractor_seekBetweenReads_outputsCues() throws Exception { CueDecoder decoder = new CueDecoder(); FakeExtractorOutput output = new FakeExtractorOutput(); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData(Util.getUtf8Bytes(TEST_DATA)) .setSimulatePartialReads(true) .build(); SubtitleExtractor extractor = new SubtitleExtractor( new WebvttDecoder(), new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build()); extractor.init(output); FakeTrackOutput [MASK] = output. [MASK] s.get(0); assertThat(extractor.read(input, null)).isNotEqualTo(Extractor.RESULT_END_OF_INPUT); extractor.seek((int) output.seekMap.getSeekPoints(2_345_000L).first.position, 2_345_000L); input.setPosition((int) output.seekMap.getSeekPoints(2_345_000L).first.position); [MASK] .clear(); while (extractor.read(input, null) != Extractor.RESULT_END_OF_INPUT) {} assertThat( [MASK] .lastFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_EXOPLAYER_CUES); assertThat( [MASK] .lastFormat.codecs).isEqualTo(MimeTypes.TEXT_VTT); assertThat( [MASK] .getSampleCount()).isEqualTo(4); // Check sample timestamps. assertThat( [MASK] .getSampleTimeUs(0)).isEqualTo(2_345_000L); assertThat( [MASK] .getSampleTimeUs(1)).isEqualTo(2_600_000L); assertThat( [MASK] .getSampleTimeUs(2)).isEqualTo(3_456_000L); assertThat( [MASK] .getSampleTimeUs(3)).isEqualTo(4_567_000L); // Check sample content. List cues0 = decoder.decode( [MASK] .getSampleData(0)); assertThat(cues0).hasSize(1); assertThat(cues0.get(0).text.toString()).isEqualTo(""This is the second subtitle.""); List cues1 = decoder.decode( [MASK] .getSampleData(1)); assertThat(cues1).hasSize(2); assertThat(cues1.get(0).text.toString()).isEqualTo(""This is the second subtitle.""); assertThat(cues1.get(1).text.toString()).isEqualTo(""This is the third subtitle.""); List cues2 = decoder.decode( [MASK] .getSampleData(2)); assertThat(cues2).hasSize(1); assertThat(cues2.get(0).text.toString()).isEqualTo(""This is the third subtitle.""); List cues3 = decoder.decode( [MASK] .getSampleData(3)); assertThat(cues3).isEmpty(); } @Test public void read_withoutInit_fails() { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[0]).build(); SubtitleExtractor extractor = new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build()); assertThrows(IllegalStateException.class, () -> extractor.read(input, null)); } @Test public void read_afterRelease_fails() { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[0]).build(); SubtitleExtractor extractor = new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build()); FakeExtractorOutput output = new FakeExtractorOutput(); extractor.init(output); extractor.release(); assertThrows(IllegalStateException.class, () -> extractor.read(input, null)); } @Test public void seek_withoutInit_fails() { SubtitleExtractor extractor = new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build()); assertThrows(IllegalStateException.class, () -> extractor.seek(0, 0)); } @Test public void seek_afterRelease_fails() { SubtitleExtractor extractor = new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build()); FakeExtractorOutput output = new FakeExtractorOutput(); extractor.init(output); extractor.release(); assertThrows(IllegalStateException.class, () -> extractor.seek(0, 0)); } @Test public void released_calledTwice() { SubtitleExtractor extractor = new SubtitleExtractor(new WebvttDecoder(), new Format.Builder().build()); FakeExtractorOutput output = new FakeExtractorOutput(); extractor.init(output); extractor.release(); extractor.release(); // Calling realease() twice does not throw an exception. } } ","trackOutput " "// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skyframe; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode.CROSS; import static com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode.DONT_CROSS; import static com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode.REPORT_ERROR; import static com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory.danglingSymlink; import static com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory.regularFile; import static com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory.symlinkToDirectory; import static com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory.symlinkToFile; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import com.google.auto.value.AutoValue; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.devtools.build.lib.actions.ActionLookupData; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; import com.google.devtools.build.lib.actions.ArtifactRoot; import com.google.devtools.build.lib.actions.ArtifactRoot.RootType; import com.google.devtools.build.lib.actions.FileArtifactValue; import com.google.devtools.build.lib.actions.FileContentsProxy; import com.google.devtools.build.lib.actions.FileStateValue; import com.google.devtools.build.lib.actions.FileStateValue.RegularFileStateValueWithContentsProxy; import com.google.devtools.build.lib.actions.FileStateValue.RegularFileStateValueWithDigest; import com.google.devtools.build.lib.actions.FileValue; import com.google.devtools.build.lib.actions.FilesetTraversalParams.DirectTraversalRoot; import com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode; import com.google.devtools.build.lib.actions.HasDigest; import com.google.devtools.build.lib.actions.ThreadStateReceiver; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.ServerDirectories; import com.google.devtools.build.lib.analysis.util.AnalysisMock; import com.google.devtools.build.lib.clock.BlazeClock; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.NullEventHandler; import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction; import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction; import com.google.devtools.build.lib.packages.WorkspaceFileValue; import com.google.devtools.build.lib.pkgcache.PathPackageLocator; import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction; import com.google.devtools.build.lib.skyframe.PackageFunction.GlobbingStrategy; import com.google.devtools.build.lib.skyframe.PackageLookupFunction.CrossRepositoryLabelViolationStrategy; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalFunction.DanglingSymlinkException; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalFunction.RecursiveFilesystemTraversalException; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory; import com.google.devtools.build.lib.testutil.FoundationTestCase; import com.google.devtools.build.lib.testutil.TimestampGranularityUtils; import com.google.devtools.build.lib.util.io.OutErr; import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; import com.google.devtools.build.lib.vfs.FileStateKey; import com.google.devtools.build.lib.vfs.FileStatus; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.devtools.build.lib.vfs.SyscallCache; import com.google.devtools.build.skyframe.AbstractSkyKey; import com.google.devtools.build.skyframe.ErrorInfo; import com.google.devtools.build.skyframe.EvaluationContext; import com.google.devtools.build.skyframe.EvaluationProgressReceiver; import com.google.devtools.build.skyframe.EvaluationResult; import com.google.devtools.build.skyframe.GroupedDeps; import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator; import com.google.devtools.build.skyframe.MemoizingEvaluator; import com.google.devtools.build.skyframe.RecordingDifferencer; import com.google.devtools.build.skyframe.SequencedRecordingDifferencer; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyFunctionException; import com.google.devtools.build.skyframe.SkyFunctionException.Transience; import com.google.devtools.build.skyframe.SkyFunctionName; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import javax.annotation.Nullable; import net.starlark.java.eval.StarlarkSemantics; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link RecursiveFilesystemTraversalFunction}. */ @RunWith(JUnit4.class) public final class RecursiveFilesystemTraversalFunctionTest extends FoundationTestCase { private static final HasDigest EMPTY_METADATA = HasDigest.EMPTY; private RecordingEvaluationProgressReceiver progressReceiver; private MemoizingEvaluator evaluator; private RecordingDifferencer differencer; private AtomicReference pkgLocator; private NonHermeticArtifactFakeFunction artifactFunction; private List artifacts; @Before public void setUp() { artifacts = new ArrayList<>(); AnalysisMock analysisMock = AnalysisMock.get(); pkgLocator = new AtomicReference<>( new PathPackageLocator( outputBase, ImmutableList.of(Root.fromPath(rootDirectory)), BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY)); AtomicReference> deletedPackages = new AtomicReference<>(ImmutableSet.of()); BlazeDirectories directories = new BlazeDirectories( new ServerDirectories(rootDirectory, outputBase, rootDirectory), rootDirectory, null, analysisMock.getProductName()); ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting( pkgLocator, ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS, directories); ConfiguredRuleClassProvider ruleClassProvider = analysisMock.createRuleClassProvider(); Map skyFunctions = new HashMap<>(); skyFunctions.put( FileStateKey.FILE_STATE, new FileStateFunction( Suppliers.ofInstance(new TimestampGranularityMonitor(BlazeClock.instance())), SyscallCache.NO_CACHE, externalFilesHelper)); skyFunctions.put(FileValue.FILE, new FileFunction(pkgLocator, directories)); skyFunctions.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction()); skyFunctions.put( SkyFunctions.DIRECTORY_LISTING_STATE, new DirectoryListingStateFunction(externalFilesHelper, SyscallCache.NO_CACHE)); skyFunctions.put( SkyFunctions.RECURSIVE_FILESYSTEM_TRAVERSAL, new RecursiveFilesystemTraversalFunction(SyscallCache.NO_CACHE)); skyFunctions.put( SkyFunctions.PACKAGE_LOOKUP, new PackageLookupFunction( deletedPackages, CrossRepositoryLabelViolationStrategy.ERROR, BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY, BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER)); skyFunctions.put( SkyFunctions.IGNORED_PACKAGE_PREFIXES, new IgnoredPackagePrefixesFunction( /*ignoredPackagePrefixesFile=*/ PathFragment.EMPTY_FRAGMENT)); skyFunctions.put( SkyFunctions.PACKAGE, new PackageFunction( null, null, null, null, null, /*packageProgress=*/ null, PackageFunction.ActionOnIOExceptionReadingBuildFile.UseOriginalIOException.INSTANCE, /* shouldUseRepoDotBazel= */ true, GlobbingStrategy.SKYFRAME_HYBRID, k -> ThreadStateReceiver.NULL_INSTANCE)); skyFunctions.put( WorkspaceFileValue.WORKSPACE_FILE, new WorkspaceFileFunction( ruleClassProvider, analysisMock .getPackageFactoryBuilderForTesting(directories) .build(ruleClassProvider, fileSystem), directories, /*bzlLoadFunctionForInlining=*/ null)); skyFunctions.put( SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER)); skyFunctions.put( SkyFunctions.LOCAL_REPOSITORY_LOOKUP, new LocalRepositoryLookupFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER)); skyFunctions.put( FileSymlinkInfiniteExpansionUniquenessFunction.NAME, new FileSymlinkInfiniteExpansionUniquenessFunction()); skyFunctions.put( FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction()); // We use a non-hermetic key to allow us to invalidate the proper artifacts on rebuilds. We // could have the artifact depend on the corresponding FileValue, but that would not cover the // case of a generated directory, which we have test coverage for. skyFunctions.put(Artifact.ARTIFACT, new ArtifactFakeFunction()); artifactFunction = new NonHermeticArtifactFakeFunction(); skyFunctions.put(SkyFunctions.ACTION_EXECUTION, new ActionFakeFunction()); skyFunctions.put(NONHERMETIC_ARTIFACT, artifactFunction); progressReceiver = new RecordingEvaluationProgressReceiver(); differencer = new SequencedRecordingDifferencer(); evaluator = new InMemoryMemoizingEvaluator(skyFunctions, differencer, progressReceiver); PrecomputedValue.BUILD_ID.set(differencer, UUID.randomUUID()); PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get()); PrecomputedValue.STARLARK_SEMANTICS.set(differencer, StarlarkSemantics.DEFAULT); } private Artifact sourceArtifact(String path) { return ActionsTestUtil.createArtifact( ArtifactRoot.asSourceRoot(Root.fromPath(rootDirectory)), path); } private Artifact sourceArtifactUnderPackagePath(String path, String packagePath) { return ActionsTestUtil.createArtifact( ArtifactRoot.asSourceRoot(Root.fromPath(rootDirectory.getRelative(packagePath))), path); } private SpecialArtifact treeArtifact(String path) { return ActionsTestUtil.createTreeArtifactWithGeneratingAction( ArtifactRoot.asDerivedRoot(rootDirectory, RootType.Output, ""out""), PathFragment.create(""out/"" + path)); } private void addNewTreeFileArtifact(SpecialArtifact parent, String relatedPath) throws IOException { TreeFileArtifact treeFileArtifact = TreeFileArtifact.createTreeOutput(parent, relatedPath); artifactFunction.addNewTreeFileArtifact(treeFileArtifact); } private Artifact derivedArtifact(String path) { PathFragment execPath = PathFragment.create(""out"").getRelative(path); Artifact.DerivedArtifact result = (Artifact.DerivedArtifact) ActionsTestUtil.createArtifactWithExecPath( ArtifactRoot.asDerivedRoot(rootDirectory, RootType.Output, ""out""), execPath); result.setGeneratingActionKey( ActionLookupData.create(ActionsTestUtil.NULL_ARTIFACT_OWNER, artifacts.size())); artifacts.add(result); return result; } private static RootedPath rootedPath(Artifact artifact) { return RootedPath.toRootedPath(artifact.getRoot().getRoot(), artifact.getRootRelativePath()); } private RootedPath rootedPath(String path, String packagePath) { return RootedPath.toRootedPath( Root.fromPath(rootDirectory.getRelative(packagePath)), PathFragment.create(path)); } private static RootedPath childOf(Artifact artifact, String relative) { return RootedPath.toRootedPath( artifact.getRoot().getRoot(), artifact.getRootRelativePath().getRelative(relative)); } private static RootedPath childOf(RootedPath path, String relative) { return RootedPath.toRootedPath( path.getRoot(), path.getRootRelativePath().getRelative(relative)); } private static RootedPath parentOf(RootedPath path) { return checkNotNull(path.getParentDirectory()); } private static RootedPath siblingOf(RootedPath path, String relative) { PathFragment parent = checkNotNull(path.getRootRelativePath().getParentDirectory()); return RootedPath.toRootedPath(path.getRoot(), parent.getRelative(relative)); } private static RootedPath siblingOf(Artifact artifact, String relative) { PathFragment parent = checkNotNull(artifact.getRootRelativePath().getParentDirectory()); return RootedPath.toRootedPath(artifact.getRoot().getRoot(), parent.getRelative(relative)); } private void createFile(Path path, String... contents) throws Exception { if (!path.getParentDirectory().exists()) { scratch.dir(path.getParentDirectory().getPathString()); } scratch.file(path.getPathString(), contents); } private void createFile(Artifact artifact, String... contents) throws Exception { createFile(artifact.getPath(), contents); } private RootedPath createFile(RootedPath path, String... contents) throws Exception { scratch.dir(parentOf(path).asPath().getPathString()); createFile(path.asPath(), contents); return path; } private static TraversalRequest fileLikeRoot( Artifact file, PackageBoundaryMode pkgBoundaryMode, boolean strictOutput, boolean emitEmptyDirectoryNodes) { return new AutoValue_RecursiveFilesystemTraversalFunctionTest_BasicTraversalRequest( DirectTraversalRoot.forFileOrDirectory(file), /*isRootGenerated=*/ !file.isSourceArtifact(), pkgBoundaryMode, strictOutput, /*skipTestingForSubpackage=*/ false, emitEmptyDirectoryNodes); } private static TraversalRequest fileLikeRoot( Artifact file, PackageBoundaryMode pkgBoundaryMode, boolean strictOutput) { return fileLikeRoot(file, pkgBoundaryMode, strictOutput, /*emitEmptyDirectoryNodes=*/ false); } private static TraversalRequest fileLikeRoot(Artifact file, PackageBoundaryMode pkgBoundaryMode) { return fileLikeRoot(file, pkgBoundaryMode, false); } private static TraversalRequest pkgRoot( RootedPath pkgDirectory, PackageBoundaryMode pkgBoundaryMode) { return new AutoValue_RecursiveFilesystemTraversalFunctionTest_BasicTraversalRequest( DirectTraversalRoot.forRootedPath(pkgDirectory), /*isRootGenerated=*/ false, pkgBoundaryMode, /*strictOutputFiles=*/ false, /*skipTestingForSubpackage=*/ true, /*emitEmptyDirectoryNodes=*/ false); } @AutoValue abstract static class BasicTraversalRequest extends TraversalRequest { @Override protected final String errorInfo() { return """"; } @Override protected final TraversalRequest duplicateWithOverrides( DirectTraversalRoot root, boolean skipTestingForSubpackage) { return new AutoValue_RecursiveFilesystemTraversalFunctionTest_BasicTraversalRequest( root, isRootGenerated(), crossPkgBoundaries(), strictOutputFiles(), skipTestingForSubpackage, emitEmptyDirectoryNodes()); } } private EvaluationResult eval(SkyKey key) throws Exception { EvaluationContext evaluationContext = EvaluationContext.newBuilder() .setKeepGoing(false) .setParallelism(SkyframeExecutor.DEFAULT_THREAD_COUNT) .setEventHandler(NullEventHandler.INSTANCE) .build(); return evaluator.evaluate(ImmutableList.of(key), evaluationContext); } private RecursiveFilesystemTraversalValue evalTraversalRequest(TraversalRequest params) throws Exception { EvaluationResult result = eval(params); assertThat(result.hasError()).isFalse(); return result.get(params); } /** * Asserts that the requested SkyValue can be built and results in the expected set of files. * *

The metadata of files is ignored in comparing the actual results with the expected ones. The * returned object however contains the actual metadata. */ private RecursiveFilesystemTraversalValue traverseAndAssertFiles( TraversalRequest params, ResolvedFile... expectedFilesIgnoringMetadata) throws Exception { RecursiveFilesystemTraversalValue result = evalTraversalRequest(params); Map nameToActualResolvedFiles = new HashMap<>(); for (ResolvedFile act : result.getTransitiveFiles().toList()) { // We can't compare directly, since metadata would be different, so we compare // by comparing the results of public method calls.. nameToActualResolvedFiles.put(act.getNameInSymlinkTree(), act); } assertExpectedResolvedFilesPresent(nameToActualResolvedFiles, expectedFilesIgnoringMetadata); return result; } private static void assertExpectedResolvedFilesPresent( Map nameToActualResolvedFiles, ResolvedFile... expectedFilesIgnoringMetadata) throws Exception { assertWithMessage(""Expected files "" + Arrays.toString(expectedFilesIgnoringMetadata)) .that(nameToActualResolvedFiles) .hasSize(expectedFilesIgnoringMetadata.length); assertEquals( ""Unequal number of ResolvedFiles in Actual and expected."", expectedFilesIgnoringMetadata.length, nameToActualResolvedFiles.size()); for (ResolvedFile expected : expectedFilesIgnoringMetadata) { ResolvedFile actual = nameToActualResolvedFiles.get(expected.getNameInSymlinkTree()); assertEquals(expected.getType(), actual.getType()); assertEquals(expected.getPath(), actual.getPath()); assertEquals(expected.getTargetInSymlinkTree(false), actual.getTargetInSymlinkTree(false)); try { expected.getTargetInSymlinkTree(true); // No exception thrown, let's safely compare results. assertEquals(expected.getTargetInSymlinkTree(true), actual.getTargetInSymlinkTree(true)); } catch (DanglingSymlinkException e) { assertThrows( ""Expected exception not thrown while requesting resolved symlink."", DanglingSymlinkException.class, () -> actual.getTargetInSymlinkTree(true)); } } } private void appendToFile(RootedPath rootedPath, SkyKey toInvalidate, String content) throws Exception { Path path = rootedPath.asPath(); if (path.exists()) { try (OutputStream os = path.getOutputStream(/*append=*/ true)) { os.write(content.getBytes(StandardCharsets.UTF_8)); } differencer.invalidate(ImmutableList.of(toInvalidate)); } else { createFile(path, content); } } private void appendToFile(RootedPath rootedPath, String content) throws Exception { appendToFile(rootedPath, FileStateValue.key(rootedPath), content); } private void appendToFile(Artifact file, String content) throws Exception { SkyKey key = file.isSourceArtifact() ? FileStateValue.key(rootedPath(file)) : new NonHermeticArtifactSkyKey(file); appendToFile(rootedPath(file), key, content); } private void invalidateDirectory(RootedPath path) { differencer.invalidate(ImmutableList.of(DirectoryListingStateValue.key(path))); } private void invalidateDirectory(Artifact directoryArtifact) { invalidateDirectory(rootedPath(directoryArtifact)); } private void invalidateOutputArtifact(Artifact output) { assertThat(output.isSourceArtifact()).isFalse(); differencer.invalidate(ImmutableList.of(new NonHermeticArtifactSkyKey(output))); } private static final class RecordingEvaluationProgressReceiver implements EvaluationProgressReceiver { Set invalidations; Set evaluations; RecordingEvaluationProgressReceiver() { clear(); } void clear() { invalidations = Sets.newConcurrentHashSet(); evaluations = Sets.newConcurrentHashSet(); } @Override public void invalidated(SkyKey skyKey, InvalidationState state) { invalidations.add(skyKey); } @Override public void evaluated( SkyKey skyKey, @Nullable SkyValue newValue, @Nullable ErrorInfo newError, Supplier evaluationSuccessState, EvaluationState state, @Nullable GroupedDeps directDeps) { if (evaluationSuccessState.get().succeeded()) { evaluations.add(skyKey); } } } private static void assertTraversalRootHashesAre( boolean equal, RecursiveFilesystemTraversalValue a, RecursiveFilesystemTraversalValue b) { if (equal) { assertThat(a.getResolvedRoot().get().hashCode()) .isEqualTo(b.getResolvedRoot().get().hashCode()); } else { assertThat(a.getResolvedRoot().get().hashCode()) .isNotEqualTo(b.getResolvedRoot().get().hashCode()); } } private static void assertTraversalRootHashesAreEqual( RecursiveFilesystemTraversalValue a, RecursiveFilesystemTraversalValue b) { assertTraversalRootHashesAre(true, a, b); } private static void assertTraversalRootHashesAreNotEqual( RecursiveFilesystemTraversalValue a, RecursiveFilesystemTraversalValue b) { assertTraversalRootHashesAre(false, a, b); } private void assertTraversalOfFile(Artifact rootArtifact, boolean strictOutput) throws Exception { TraversalRequest traversalRoot = fileLikeRoot(rootArtifact, DONT_CROSS, strictOutput); RootedPath rootedPath = createFile(rootedPath(rootArtifact), ""foo""); // Assert that the SkyValue is built and looks right. ResolvedFile expected = regularFile(rootedPath, EMPTY_METADATA); RecursiveFilesystemTraversalValue v1 = traverseAndAssertFiles(traversalRoot, expected); assertThat(progressReceiver.invalidations).isEmpty(); assertThat(progressReceiver.evaluations).contains(traversalRoot); progressReceiver.clear(); // Edit the file and verify that the value is rebuilt. appendToFile(rootArtifact, ""bar""); RecursiveFilesystemTraversalValue v2 = traverseAndAssertFiles(traversalRoot, expected); assertThat(progressReceiver.invalidations).contains(traversalRoot); assertThat(progressReceiver.evaluations).contains(traversalRoot); assertThat(v2).isNotEqualTo(v1); assertTraversalRootHashesAreNotEqual(v1, v2); progressReceiver.clear(); } @Test public void testTraversalOfSourceFile() throws Exception { assertTraversalOfFile(sourceArtifact(""foo/bar.txt""), false); } @Test public void testTraversalOfGeneratedFile() throws Exception { assertTraversalOfFile(derivedArtifact(""foo/bar.txt""), false); } @Test public void testTraversalOfGeneratedFileWithStrictOutput() throws Exception { assertTraversalOfFile(derivedArtifact(""foo/bar.txt""), true); } @Test public void testTraversalOfSymlinkToFile() throws Exception { Artifact linkNameArtifact = sourceArtifact(""foo/baz/qux.sym""); Artifact linkTargetArtifact = sourceArtifact(""foo/bar/baz.txt""); PathFragment linkValue = PathFragment.create(""../bar/baz.txt""); TraversalRequest traversalRoot = fileLikeRoot(linkNameArtifact, DONT_CROSS); createFile(linkTargetArtifact); scratch.dir(linkNameArtifact.getExecPath().getParentDirectory().getPathString()); rootDirectory.getRelative(linkNameArtifact.getExecPath()).createSymbolicLink(linkValue); // Assert that the SkyValue is built and looks right. RootedPath symlinkNamePath = rootedPath(linkNameArtifact); RootedPath symlinkTargetPath = rootedPath(linkTargetArtifact); ResolvedFile expected = symlinkToFile(symlinkTargetPath, symlinkNamePath, linkValue, EMPTY_METADATA); RecursiveFilesystemTraversalValue v1 = traverseAndAssertFiles(traversalRoot, expected); assertThat(progressReceiver.invalidations).isEmpty(); assertThat(progressReceiver.evaluations).contains(traversalRoot); progressReceiver.clear(); // Edit the target of the symlink and verify that the value is rebuilt. appendToFile(linkTargetArtifact, ""bar""); RecursiveFilesystemTraversalValue v2 = traverseAndAssertFiles(traversalRoot, expected); assertThat(progressReceiver.invalidations).contains(traversalRoot); assertThat(progressReceiver.evaluations).contains(traversalRoot); assertThat(v2).isNotEqualTo(v1); assertTraversalRootHashesAreNotEqual(v1, v2); } @Test public void testTraversalOfTransitiveSymlinkToFile() throws Exception { Artifact directLinkArtifact = sourceArtifact(""direct/file.sym""); Artifact transitiveLinkArtifact = sourceArtifact(""transitive/sym.sym""); RootedPath fileA = createFile(rootedPath(sourceArtifact(""a/file.a""))); RootedPath directLink = rootedPath(directLinkArtifact); RootedPath transitiveLink = rootedPath(transitiveLinkArtifact); PathFragment directLinkPath = PathFragment.create(""../a/file.a""); PathFragment transitiveLinkPath = PathFragment.create(""../direct/file.sym""); parentOf(directLink).asPath().createDirectory(); parentOf(transitiveLink).asPath().createDirectory(); directLink.asPath().createSymbolicLink(directLinkPath); transitiveLink.asPath().createSymbolicLink(transitiveLinkPath); traverseAndAssertFiles( fileLikeRoot(directLinkArtifact, DONT_CROSS), symlinkToFile(fileA, directLink, directLinkPath, EMPTY_METADATA)); traverseAndAssertFiles( fileLikeRoot(transitiveLinkArtifact, DONT_CROSS), symlinkToFile(fileA, transitiveLink, transitiveLinkPath, EMPTY_METADATA)); } private void assertTraversalOfDirectory(Artifact directoryArtifact) throws Exception { // Create files under the directory. // Use the root + root-relative path of the rootArtifact to create these files, rather than // using the rootDirectory + execpath of the rootArtifact. The resulting paths are the same // but the RootedPaths are different: // in the 1st case, it is: RootedPath(/root/execroot, relative), in the second it is // in the 2nd case, it is: RootedPath(/root, execroot/relative). // Creating the files will also create the parent directories. RootedPath file1 = createFile(childOf(directoryArtifact, ""bar.txt"")); RootedPath file2; if (directoryArtifact.isTreeArtifact()) { file2 = createFile(childOf(directoryArtifact, ""qux.txt"")); addNewTreeFileArtifact((SpecialArtifact) directoryArtifact, ""bar.txt""); addNewTreeFileArtifact((SpecialArtifact) directoryArtifact, ""qux.txt""); } else { file2 = createFile(childOf(directoryArtifact, ""baz/qux.txt"")); } TraversalRequest traversalRoot = fileLikeRoot(directoryArtifact, DONT_CROSS); // Assert that the SkyValue is built and looks right. ResolvedFile expected1 = regularFile(file1, EMPTY_METADATA); ResolvedFile expected2 = regularFile(file2, EMPTY_METADATA); RecursiveFilesystemTraversalValue v1 = traverseAndAssertFiles(traversalRoot, expected1, expected2); assertThat(progressReceiver.invalidations).isEmpty(); assertThat(progressReceiver.evaluations).contains(traversalRoot); progressReceiver.clear(); // Add a new file to the directory and see that the value is rebuilt. TimestampGranularityUtils.waitForTimestampGranularity( directoryArtifact.getPath().stat().getLastChangeTime(), OutErr.SYSTEM_OUT_ERR); RootedPath file3 = createFile(childOf(directoryArtifact, ""foo.txt"")); if (directoryArtifact.isTreeArtifact()) { addNewTreeFileArtifact((SpecialArtifact) directoryArtifact, ""foo.txt""); } if (directoryArtifact.isSourceArtifact()) { invalidateDirectory(directoryArtifact); } else { invalidateOutputArtifact(directoryArtifact); } ResolvedFile expected3 = regularFile(file3, EMPTY_METADATA); RecursiveFilesystemTraversalValue v2 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3); assertThat(progressReceiver.invalidations).contains(traversalRoot); assertThat(progressReceiver.evaluations).contains(traversalRoot); // Directories always have the same hash code, but that is fine because their contents are also // part of the RecursiveFilesystemTraversalValue, so v1 and v2 are unequal. assertThat(v2).isNotEqualTo(v1); assertTraversalRootHashesAreEqual(v1, v2); progressReceiver.clear(); // Edit a file in the directory and see that the value is rebuilt. RecursiveFilesystemTraversalValue v3; if (directoryArtifact.isSourceArtifact()) { SkyKey toInvalidate = FileStateValue.key(file1); appendToFile(file1, toInvalidate, ""bar""); v3 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3); assertThat(progressReceiver.invalidations).contains(traversalRoot); assertThat(progressReceiver.evaluations).contains(traversalRoot); assertThat(v3).isNotEqualTo(v2); // Directories always have the same hash code, but that is fine because their contents are // also part of the RecursiveFilesystemTraversalValue, so v2 and v3 are unequal. assertTraversalRootHashesAreEqual(v2, v3); progressReceiver.clear(); } else { // Dependency checking of output directories is unsound. Specifically, the directory mtime // is not changed when a contained file is modified. v3 = v2; } // Add a new file *outside* of the directory and see that the value is *not* rebuilt. Artifact someFile = sourceArtifact(""somewhere/else/a.file""); createFile(someFile, ""new file""); appendToFile(someFile, ""not all changes are treated equal""); RecursiveFilesystemTraversalValue v4 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3); assertThat(v4).isSameInstanceAs(v3); assertTraversalRootHashesAreEqual(v3, v4); assertThat(progressReceiver.invalidations).doesNotContain(traversalRoot); // Add a new empty subdirectory to the directory and see that the value is rebuilt, but results // in the collection of files. // TODO(#15901): Empty directories currently aren't representable as tree artifact contents and // thus aren't tested here. if (!directoryArtifact.isTreeArtifact()) { childOf(directoryArtifact, ""empty_dir"").asPath().createDirectory(); if (directoryArtifact.isSourceArtifact()) { invalidateDirectory(directoryArtifact); } else { invalidateOutputArtifact(directoryArtifact); } RecursiveFilesystemTraversalValue v5 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3); assertThat(v5.getResolvedRoot()).isEqualTo(v4.getResolvedRoot()); assertThat(v5.getTransitiveFiles().toList()) .containsExactlyElementsIn(v4.getTransitiveFiles().toList()); assertThat(progressReceiver.invalidations).contains(traversalRoot); } } @Test public void testTraversalOfSourceDirectory() throws Exception { assertTraversalOfDirectory(sourceArtifact(""dir"")); } @Test public void testTraversalOfSourceTreeArtifact() throws Exception { assertTraversalOfDirectory(treeArtifact(""dir"")); } // Note that in actual Bazel derived artifact directories are not checked for modifications on // incremental builds by default. See TrackSourceDirectoriesFlag. @Test public void testTraversalOfGeneratedDirectory() throws Exception { assertTraversalOfDirectory(derivedArtifact(""dir"")); } @Test public void testTraversalOfSourceDirectoryWithEmptyDirectoryNodes() throws Exception { Artifact directoryArtifact = sourceArtifact(""dir""); directoryArtifact.getPath().createDirectoryAndParents(); TraversalRequest traversalRoot = fileLikeRoot( directoryArtifact, DONT_CROSS, /*strictOutput=*/ false, /*emitEmptyDirectoryNodes=*/ true); // Assert that the SkyValue is built and looks right. ResolvedFile rootNode = ResolvedFileFactory.directory( RootedPath.toRootedPath( directoryArtifact.getRoot().getRoot(), directoryArtifact.getRootRelativePath())); RecursiveFilesystemTraversalValue v1 = traverseAndAssertFiles(traversalRoot, rootNode); assertThat(progressReceiver.invalidations).isEmpty(); assertThat(progressReceiver.evaluations).contains(traversalRoot); progressReceiver.clear(); // Add a new file to the directory and see that the value is rebuilt. RootedPath emptyDir = childOf(directoryArtifact, ""empty_dir""); emptyDir.asPath().createDirectory(); ResolvedFile emptyDirNode = ResolvedFileFactory.directory(emptyDir); invalidateDirectory(directoryArtifact); // The value only contains nodes for empty directories - the root dir is no longer empty at this // point and thus not represented as a node. RecursiveFilesystemTraversalValue v2 = traverseAndAssertFiles(traversalRoot, emptyDirNode); assertThat(v2).isNotEqualTo(v1); assertTraversalRootHashesAreEqual(v1, v2); assertThat(progressReceiver.invalidations).contains(traversalRoot); assertThat(progressReceiver.evaluations).contains(traversalRoot); } @Test public void testTraversalOfTransitiveSymlinkToDirectory() throws Exception { Artifact directLinkArtifact = sourceArtifact(""direct/dir.sym""); Artifact transitiveLinkArtifact = sourceArtifact(""transitive/sym.sym""); RootedPath fileA = createFile(rootedPath(sourceArtifact(""a/file.a""))); RootedPath directLink = rootedPath(directLinkArtifact); RootedPath transitiveLink = rootedPath(transitiveLinkArtifact); PathFragment directLinkPath = PathFragment.create(""../a""); PathFragment transitiveLinkPath = PathFragment.create(""../direct/dir.sym""); parentOf(directLink).asPath().createDirectory(); parentOf(transitiveLink).asPath().createDirectory(); directLink.asPath().createSymbolicLink(directLinkPath); transitiveLink.asPath().createSymbolicLink(transitiveLinkPath); // Expect the file as if was a child of the direct symlink, not of the actual directory. traverseAndAssertFiles( fileLikeRoot(directLinkArtifact, DONT_CROSS), symlinkToDirectory(parentOf(fileA), directLink, directLinkPath, EMPTY_METADATA), regularFile(childOf(directLinkArtifact, ""file.a""), EMPTY_METADATA)); // Expect the file as if was a child of the transitive symlink, not of the actual directory. traverseAndAssertFiles( fileLikeRoot(transitiveLinkArtifact, DONT_CROSS), symlinkToDirectory(parentOf(fileA), transitiveLink, transitiveLinkPath, EMPTY_METADATA), regularFile(childOf(transitiveLinkArtifact, ""file.a""), EMPTY_METADATA)); } @Test public void testTraversePackage() throws Exception { Artifact buildFile = sourceArtifact(""pkg/BUILD""); RootedPath buildFilePath = createFile(rootedPath(buildFile)); RootedPath file1 = createFile(siblingOf(buildFile, ""subdir/file.a"")); traverseAndAssertFiles( pkgRoot(parentOf(buildFilePath), DONT_CROSS), regularFile(buildFilePath, EMPTY_METADATA), regularFile(file1, EMPTY_METADATA)); } @Test public void testTraversalOfSymlinkToDirectory() throws Exception { Artifact linkNameArtifact = sourceArtifact(""link/foo.sym""); Artifact linkTargetArtifact = sourceArtifact(""dir""); RootedPath linkName = rootedPath(linkNameArtifact); PathFragment linkValue = PathFragment.create(""../dir""); RootedPath file1 = createFile(childOf(linkTargetArtifact, ""file.1"")); createFile(childOf(linkTargetArtifact, ""sub/file.2"")); scratch.dir(parentOf(linkName).asPath().getPathString()); linkName.asPath().createSymbolicLink(linkValue); // Assert that the SkyValue is built and looks right. TraversalRequest traversalRoot = fileLikeRoot(linkNameArtifact, DONT_CROSS); ResolvedFile expected1 = symlinkToDirectory(rootedPath(linkTargetArtifact), linkName, linkValue, EMPTY_METADATA); ResolvedFile expected2 = regularFile(childOf(linkNameArtifact, ""file.1""), EMPTY_METADATA); ResolvedFile expected3 = regularFile(childOf(linkNameArtifact, ""sub/file.2""), EMPTY_METADATA); // We expect to see all the files from the symlink'd directory, under the symlink's path, not // under the symlink target's path. RecursiveFilesystemTraversalValue v1 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3); assertThat(progressReceiver.invalidations).isEmpty(); assertThat(progressReceiver.evaluations).contains(traversalRoot); progressReceiver.clear(); // Add a new file to the directory and see that the value is rebuilt. createFile(childOf(linkTargetArtifact, ""file.3"")); invalidateDirectory(linkTargetArtifact); ResolvedFile expected4 = regularFile(childOf(linkNameArtifact, ""file.3""), EMPTY_METADATA); RecursiveFilesystemTraversalValue v2 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3, expected4); assertThat(progressReceiver.invalidations).contains(traversalRoot); assertThat(progressReceiver.evaluations).contains(traversalRoot); assertThat(v2).isNotEqualTo(v1); assertTraversalRootHashesAreNotEqual(v1, v2); progressReceiver.clear(); // Edit a file in the directory and see that the value is rebuilt. appendToFile(file1, ""bar""); RecursiveFilesystemTraversalValue v3 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3, expected4); assertThat(progressReceiver.invalidations).contains(traversalRoot); assertThat(progressReceiver.evaluations).contains(traversalRoot); assertThat(v3).isNotEqualTo(v2); assertTraversalRootHashesAreNotEqual(v2, v3); progressReceiver.clear(); // Add a new file *outside* of the directory and see that the value is *not* rebuilt. Artifact someFile = sourceArtifact(""somewhere/else/a.file""); createFile(someFile, ""new file""); appendToFile(someFile, ""not all changes are treated equal""); RecursiveFilesystemTraversalValue v4 = traverseAndAssertFiles(traversalRoot, expected1, expected2, expected3, expected4); assertThat(v4).isEqualTo(v3); assertTraversalRootHashesAreEqual(v3, v4); assertThat(progressReceiver.invalidations).doesNotContain(traversalRoot); } @Test public void testTraversalOfDanglingSymlink() throws Exception { Artifact linkArtifact = sourceArtifact(""a/dangling.sym""); RootedPath link = rootedPath(linkArtifact); PathFragment linkTarget = PathFragment.create(""non_existent""); parentOf(link).asPath().createDirectory(); link.asPath().createSymbolicLink(linkTarget); traverseAndAssertFiles( fileLikeRoot(linkArtifact, DONT_CROSS), danglingSymlink(link, linkTarget, EMPTY_METADATA)); } @Test public void testTraversalOfDanglingSymlinkInADirectory() throws Exception { Artifact [MASK] = sourceArtifact(""a""); RootedPath file = createFile(childOf( [MASK] , ""file.txt"")); RootedPath link = rootedPath(sourceArtifact(""a/dangling.sym"")); PathFragment linkTarget = PathFragment.create(""non_existent""); parentOf(link).asPath().createDirectory(); link.asPath().createSymbolicLink(linkTarget); traverseAndAssertFiles( fileLikeRoot( [MASK] , DONT_CROSS), regularFile(file, EMPTY_METADATA), danglingSymlink(link, linkTarget, EMPTY_METADATA)); } private void assertTraverseSubpackages(PackageBoundaryMode traverseSubpackages) throws Exception { Artifact pkgDirArtifact = sourceArtifact(""pkg1/foo""); Artifact subpkgDirArtifact = sourceArtifact(""pkg1/foo/subdir/subpkg""); RootedPath pkgBuildFile = childOf(pkgDirArtifact, ""BUILD""); RootedPath subpkgBuildFile = childOf(subpkgDirArtifact, ""BUILD""); scratch.dir(rootedPath(pkgDirArtifact).asPath().getPathString()); scratch.dir(rootedPath(subpkgDirArtifact).asPath().getPathString()); createFile(pkgBuildFile); createFile(subpkgBuildFile); TraversalRequest traversalRoot = pkgRoot(parentOf(pkgBuildFile), traverseSubpackages); ResolvedFile expected1 = regularFile(pkgBuildFile, EMPTY_METADATA); ResolvedFile expected2 = regularFile(subpkgBuildFile, EMPTY_METADATA); switch (traverseSubpackages) { case CROSS: traverseAndAssertFiles(traversalRoot, expected1, expected2); break; case DONT_CROSS: traverseAndAssertFiles(traversalRoot, expected1); break; case REPORT_ERROR: EvaluationResult result = eval(traversalRoot); assertThat(result.hasError()).isTrue(); assertThat(result.getError().getException()) .hasMessageThat() .contains(""crosses package boundary into package rooted at""); break; default: throw new IllegalStateException(traverseSubpackages.toString()); } } @Test public void testTraverseSubpackages() throws Exception { assertTraverseSubpackages(CROSS); } @Test public void testDoNotTraverseSubpackages() throws Exception { assertTraverseSubpackages(DONT_CROSS); } @Test public void testReportErrorWhenTraversingSubpackages() throws Exception { assertTraverseSubpackages(REPORT_ERROR); } @Test public void testSwitchPackageRootsWhenUsingMultiplePackagePaths() throws Exception { // Layout: // pp1://a/BUILD // pp1://a/file.a // pp1://a/b.sym -> b/ (only created later) // pp1://a/b/ // pp1://a/b/file.fake // pp1://a/subdir/file.b // // pp2://a/BUILD // pp2://a/b/ // pp2://a/b/BUILD // pp2://a/b/file.a // pp2://a/subdir.fake/ // pp2://a/subdir.fake/file.fake // // Notice that pp1://a/b will be overlaid by pp2://a/b as the latter has a BUILD file and that // takes precedence. On the other hand the package definition pp2://a/BUILD will be ignored // since package //a is already defined under pp1. // // Notice also that pp1://a/b.sym is a relative symlink pointing to b/. This should be resolved // to the definition of //a/b/ under pp1, not under pp2. // Set the package paths. pkgLocator.set( new PathPackageLocator( outputBase, ImmutableList.of( Root.fromPath(rootDirectory.getRelative(""pp1"")), Root.fromPath(rootDirectory.getRelative(""pp2""))), BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY)); PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get()); Artifact aBuildArtifact = sourceArtifactUnderPackagePath(""a/BUILD"", ""pp1""); Artifact bBuildArtifact = sourceArtifactUnderPackagePath(""a/b/BUILD"", ""pp2""); RootedPath pp1aBuild = createFile(rootedPath(aBuildArtifact)); RootedPath pp1aFileA = createFile(siblingOf(pp1aBuild, ""file.a"")); RootedPath pp1bFileFake = createFile(siblingOf(pp1aBuild, ""b/file.fake"")); RootedPath pp1aSubdirFileB = createFile(siblingOf(pp1aBuild, ""subdir/file.b"")); RootedPath pp2aBuild = createFile(rootedPath(""a/BUILD"", ""pp2"")); RootedPath pp2bBuild = createFile(rootedPath(bBuildArtifact)); RootedPath pp2bFileA = createFile(siblingOf(pp2bBuild, ""file.a"")); createFile(siblingOf(pp2aBuild, ""subdir.fake/file.fake"")); // Traverse //a including subpackages. The result should contain the pp1-definition of //a and // the pp2-definition of //a/b. traverseAndAssertFiles( pkgRoot(parentOf(rootedPath(aBuildArtifact)), CROSS), regularFile(pp1aBuild, EMPTY_METADATA), regularFile(pp1aFileA, EMPTY_METADATA), regularFile(pp1aSubdirFileB, EMPTY_METADATA), regularFile(pp2bBuild, EMPTY_METADATA), regularFile(pp2bFileA, EMPTY_METADATA)); // Traverse //a excluding subpackages. The result should only contain files from //a and not // from //a/b. traverseAndAssertFiles( pkgRoot(parentOf(rootedPath(aBuildArtifact)), DONT_CROSS), regularFile(pp1aBuild, EMPTY_METADATA), regularFile(pp1aFileA, EMPTY_METADATA), regularFile(pp1aSubdirFileB, EMPTY_METADATA)); // Create a relative symlink pp1://a/b.sym -> b/. It will be resolved to the subdirectory // pp1://a/b, even though a package definition pp2://a/b exists. RootedPath pp1aBsym = siblingOf(pp1aFileA, ""b.sym""); pp1aBsym.asPath().createSymbolicLink(PathFragment.create(""b"")); invalidateDirectory(parentOf(pp1aBsym)); // Traverse //a excluding subpackages. The relative symlink //a/b.sym points to the subdirectory // a/b, i.e. the pp1-definition, even though there is a pp2-defined package //a/b and we expect // to see b.sym/b.fake (not b/b.fake). traverseAndAssertFiles( pkgRoot(parentOf(rootedPath(aBuildArtifact)), DONT_CROSS), regularFile(pp1aBuild, EMPTY_METADATA), regularFile(pp1aFileA, EMPTY_METADATA), regularFile(childOf(pp1aBsym, ""file.fake""), EMPTY_METADATA), symlinkToDirectory( parentOf(pp1bFileFake), pp1aBsym, PathFragment.create(""b""), EMPTY_METADATA), regularFile(pp1aSubdirFileB, EMPTY_METADATA)); } @Test public void testFileDigestChangeCausesRebuild() throws Exception { Artifact artifact = sourceArtifact(""foo/bar.txt""); RootedPath path = rootedPath(artifact); createFile(path, ""hello""); // Assert that the SkyValue is built and looks right. TraversalRequest params = fileLikeRoot(artifact, DONT_CROSS); ResolvedFile expected = regularFile(path, EMPTY_METADATA); RecursiveFilesystemTraversalValue v1 = traverseAndAssertFiles(params, expected); assertThat(progressReceiver.evaluations).contains(params); progressReceiver.clear(); // Change the digest of the file. See that the value is rebuilt. appendToFile(path, ""world""); RecursiveFilesystemTraversalValue v2 = traverseAndAssertFiles(params, expected); assertThat(progressReceiver.invalidations).contains(params); assertThat(v2).isNotEqualTo(v1); assertTraversalRootHashesAreNotEqual(v1, v2); } @Test public void testFileMtimeChangeDoesNotCauseRebuildIfDigestIsUnchanged() throws Exception { Artifact artifact = sourceArtifact(""foo/bar.txt""); RootedPath path = rootedPath(artifact); createFile(path, ""hello""); // Assert that the SkyValue is built and looks right. TraversalRequest params = fileLikeRoot(artifact, DONT_CROSS); ResolvedFile expected = regularFile(path, EMPTY_METADATA); RecursiveFilesystemTraversalValue v1 = traverseAndAssertFiles(params, expected); assertThat(progressReceiver.evaluations).contains(params); progressReceiver.clear(); // Change the mtime of the file but not the digest. See that the value is *not* rebuilt. TimestampGranularityUtils.waitForTimestampGranularity( path.asPath().stat().getLastChangeTime(), OutErr.SYSTEM_OUT_ERR); path.asPath().setLastModifiedTime(System.currentTimeMillis()); RecursiveFilesystemTraversalValue v2 = traverseAndAssertFiles(params, expected); assertThat(v2).isEqualTo(v1); assertTraversalRootHashesAreEqual(v1, v2); } @Test public void testGeneratedDirectoryConflictsWithPackage() throws Exception { Artifact genDir = derivedArtifact(""a/b""); createFile(rootedPath(sourceArtifact(""a/b/c/file.real""))); createFile(rootedPath(derivedArtifact(""a/b/c/file.fake""))); createFile(sourceArtifact(""a/b/c/BUILD"")); SkyKey key = fileLikeRoot(genDir, CROSS); EvaluationResult result = eval(key); assertThat(result.hasError()).isTrue(); ErrorInfo error = result.getError(key); assertThat(error.isTransitivelyTransient()).isFalse(); assertThat(error.getException()) .hasMessageThat() .contains(""Generated directory a/b/c conflicts with package under the same path.""); } @Test public void unboundedSymlinkExpansionError() throws Exception { Artifact bazLink = sourceArtifact(""foo/baz.sym""); Path parentDir = scratch.dir(""foo""); bazLink.getPath().createSymbolicLink(parentDir); SkyKey key = pkgRoot(parentOf(rootedPath(bazLink)), DONT_CROSS); EvaluationResult result = eval(key); assertThat(result.hasError()).isTrue(); ErrorInfo error = result.getError(key); assertThat(error.getException()).isInstanceOf(RecursiveFilesystemTraversalException.class); assertThat(((RecursiveFilesystemTraversalException) error.getException()).getType()) .isEqualTo(RecursiveFilesystemTraversalException.Type.SYMLINK_CYCLE_OR_INFINITE_EXPANSION); assertThat(error.getException()).hasMessageThat().contains(""Infinite symlink expansion""); } @Test public void symlinkChainError() throws Exception { scratch.dir(""a""); Artifact fooLink = sourceArtifact(""a/foo.sym""); Artifact barLink = sourceArtifact(""a/bar.sym""); Artifact bazLink = sourceArtifact(""a/baz.sym""); fooLink.getPath().createSymbolicLink(barLink.getPath()); barLink.getPath().createSymbolicLink(bazLink.getPath()); bazLink.getPath().createSymbolicLink(fooLink.getPath()); SkyKey key = pkgRoot(parentOf(rootedPath(bazLink)), DONT_CROSS); EvaluationResult result = eval(key); assertThat(result.hasError()).isTrue(); ErrorInfo error = result.getError(key); assertThat(error.getException()).isInstanceOf(RecursiveFilesystemTraversalException.class); assertThat(((RecursiveFilesystemTraversalException) error.getException()).getType()) .isEqualTo(RecursiveFilesystemTraversalException.Type.SYMLINK_CYCLE_OR_INFINITE_EXPANSION); assertThat(error.getException()).hasMessageThat().contains(""Symlink cycle""); } private static final class NonHermeticArtifactFakeFunction implements SkyFunction { private TreeArtifactValue.Builder tree; @Override public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException { try { if (skyKey.argument() instanceof Artifact && ((Artifact) skyKey.argument()).isTreeArtifact()) { return tree.build(); } return FileArtifactValue.createForTesting(((Artifact) skyKey.argument()).getPath()); } catch (IOException e) { throw new SkyFunctionException(e, Transience.PERSISTENT){}; } } void addNewTreeFileArtifact(TreeFileArtifact input) throws IOException { if (tree == null) { tree = TreeArtifactValue.newBuilder(input.getParent()); } tree.putChild(input, FileArtifactValue.createForTesting(input.getPath())); } } private static final class ArtifactFakeFunction implements SkyFunction { @Override public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException { return env.getValue(new NonHermeticArtifactSkyKey(skyKey)); } } private final class ActionFakeFunction implements SkyFunction { @Nullable @Override public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException { return env.getValue( new NonHermeticArtifactSkyKey( checkNotNull(artifacts.get(((ActionLookupData) skyKey).getActionIndex()), skyKey))); } } @Test public void testFileArtifactValueRetainsData() throws Exception { Artifact artifact = derivedArtifact(""foo/fooy.txt""); Artifact strictArtifact = derivedArtifact(""goo/gooy.txt""); createFile(rootedPath(artifact), ""fooy""); createFile(rootedPath(strictArtifact), ""gooy""); TraversalRequest request = fileLikeRoot(artifact, DONT_CROSS, false); TraversalRequest strictRequest = fileLikeRoot(strictArtifact, DONT_CROSS, true); EvaluationResult result = eval(request); EvaluationResult strictResult = eval(strictRequest); assertThat(result.values()).hasSize(1); assertThat(strictResult.values()).hasSize(1); RecursiveFilesystemTraversalValue value = result.values().iterator().next(); RecursiveFilesystemTraversalValue strictValue = strictResult.values().iterator().next(); ResolvedFile resolvedFile = value.getResolvedRoot().get(); ResolvedFile strictResolvedFile = strictValue.getResolvedRoot().get(); assertThat(resolvedFile.getMetadata()).isInstanceOf(FileArtifactValue.class); assertThat(strictResolvedFile.getMetadata()).isInstanceOf(FileArtifactValue.class); } @Test public void testWithDigestFileArtifactValue() throws Exception { // file artifacts will return the same bytes as it was initialized with byte[] expectedBytes = new byte[] {1, 2, 3}; FileArtifactValue fav = FileArtifactValue.createForVirtualActionInput(expectedBytes, 10L); HasDigest result = RecursiveFilesystemTraversalFunction.withDigest(fav, null, SyscallCache.NO_CACHE); assertThat(result).isInstanceOf(FileArtifactValue.class); assertThat(result.getDigest()).isEqualTo(expectedBytes); // Directories do not have digest but the result will have a fingerprinted digest FileArtifactValue directoryFav = FileArtifactValue.createForDirectoryWithMtime(10L); HasDigest directoryResult = RecursiveFilesystemTraversalFunction.withDigest(directoryFav, null, SyscallCache.NO_CACHE); assertThat(directoryResult).isInstanceOf(HasDigest.ByteStringDigest.class); assertThat(directoryResult.getDigest()).isNotNull(); } @Test public void testWithDigestFileStateValue() throws Exception { // RegularFileStateValue with actual digest will be transformed with the same digest byte[] expectedBytes = new byte[] {1, 2, 3}; RegularFileStateValueWithDigest withDigest = new RegularFileStateValueWithDigest(/* size= */ 10L, /* digest= */ expectedBytes); HasDigest result = RecursiveFilesystemTraversalFunction.withDigest(withDigest, null, SyscallCache.NO_CACHE); assertThat(result).isInstanceOf(FileArtifactValue.class); assertThat(result.getDigest()).isEqualTo(expectedBytes); // FileStateValue will be transformed with fingerprinted digest RootedPath rootedPath = rootedPath(""bar"", ""foo""); FileStateValue fsv = FileStateValue.create(rootedPath, SyscallCache.NO_CACHE, /*tsgm=*/ null); HasDigest fsvResult = RecursiveFilesystemTraversalFunction.withDigest(fsv, null, SyscallCache.NO_CACHE); assertThat(fsvResult).isInstanceOf(HasDigest.ByteStringDigest.class); assertThat(fsvResult.getDigest()).isNotNull(); } @Test public void testRegularFileStateValueWithoutDigest() throws Exception { Artifact artifact = derivedArtifact(""foo/fooy.txt""); RootedPath rootedPath = rootedPath(artifact); createFile(rootedPath, ""fooy-content""); FileStatus status = rootedPath.asPath().stat(); RegularFileStateValueWithContentsProxy withoutDigest = new RegularFileStateValueWithContentsProxy( status.getSize(), /* contentsProxy= */ FileContentsProxy.create(status)); HasDigest withoutDigestResult = RecursiveFilesystemTraversalFunction.withDigest( withoutDigest, rootedPath.asPath(), SyscallCache.NO_CACHE); // withDigest will construct a FileArtifactValue using the Path assertThat(withoutDigestResult).isInstanceOf(FileArtifactValue.class); assertThat(withoutDigestResult.getDigest()).isNotNull(); } @Test public void testWithDigestByteStringDigest() throws Exception { byte[] expectedBytes = new byte[] {1, 2, 3}; HasDigest.ByteStringDigest byteStringDigest = new HasDigest.ByteStringDigest(expectedBytes); HasDigest result = RecursiveFilesystemTraversalFunction.withDigest( byteStringDigest, null, SyscallCache.NO_CACHE); assertThat(result).isInstanceOf(HasDigest.ByteStringDigest.class); assertThat(result.getDigest()).isEqualTo(expectedBytes); } private static class NonHermeticArtifactSkyKey extends AbstractSkyKey { private NonHermeticArtifactSkyKey(SkyKey arg) { super(arg); } @Override public SkyFunctionName functionName() { return NONHERMETIC_ARTIFACT; } } private static final SkyFunctionName NONHERMETIC_ARTIFACT = SkyFunctionName.createNonHermetic(""NONHERMETIC_ARTIFACT""); } ","dirArtifact " "package com.alibaba.json.bvt.parser.bug; import java.util.List; import junit.framework.TestCase; import com.alibaba.fastjson.JSON; public class Bug_for_yihaodian extends TestCase { public void test_for_long_list() throws Exception { String str = ""{\""backOperatorId\"":14281,\""batchNum\"":0,\""canPurchase\"":1,\""categoryId\"":955063}""; Te ob = JSON.parseObject(str, Te.class); } public static class Te { /** 产品ID */ private Long id; /** 要删除产品的ID */ private String deletedProductId; /** 产品编码 */ private String productCode; /** 产品名 */ private String productCname; /** 产品名前面的品牌名 */ private String productBrandName; /** 产品名英文 */ private String productEname; /** 产品销售类别 */ private Integer productSaleType; /** 产品品牌Id */ private Long brandId; /** 产品品牌 */ private String brandName; /** 市场价 */ private Double productListPrice; /** 分类Id */ private Long categoryId; /** 旧分类Id */ private Long oldCategoryId; /** 旧扩展类别 **/ private Long oldExtendCategoryId; /** 厂商ID,默认为1 */ private Long mfid; /** productCanBeChange */ private Integer productCanBeChange; /** productChangeDay */ private Integer productChangeDay; /** productCanBeReturn */ private Integer productCanBeReturn; /** productReturnDay */ private Integer productReturnDay; /** 能否维修 */ private Integer productCanBeRepair; /** 能否维修 */ private Integer productCanBeRepairDay; /** 安装信息 */ private Integer productNeedInstallation; /** 条形码 */ private String ean13; /** sku */ private String sku; /** 长 */ private Double length; /** 宽 */ private Double width; /** 高 */ private Double height; /** 净重 */ private Double weight; /** keepTemperature */ private String keepTemperature; /** keepHumidity */ private String keepHumidity; /** 产品简称 */ private String productSname; /** keepSpecCondition */ private String keepSpecCondition; /** productQualityAssuranceDay */ private Integer productQualityAssuranceDay; /** 是否已删除 */ private Integer isDeleted; /** 单位 */ private String unit; /** 进价 */ private Double inPrice; /** volume */ private Double volume; /** countryOfOrgn */ private Long countryOfOrgn; /** 主图ID */ private Long defaultPictureId; /** 主图URL */ private String defaultPictureUrl; /** color */ private String color; /** currencyId */ private Long currencyId; /** 毛重 */ private Double grossWeight; /** format */ private String format; /** 易碎品 0 不是 1是 */ private String isFragile; /** 向上0 不是 1是 */ private String putOnDirection; /** 贵重品0 不是 1是 */ private String isValuables; /** 液体0 不是 1是 */ private String isLiquid; /** 防交叉污染0 不是 1是 */ private String isCrossContamination; /** 16进制的颜色代码,如#FF00AA */ private String colorNumber; /** 尺码 */ private String productSize; /** 替换后的尺码 */ private String replaceProductSize; /** 销售技巧 */ private String saleSkill; /** 本商品作为赠品时的处理方法 */ private String dispositionInstruct; /** 产地 */ private String placeOfOrigin; /** 产品页面标题 */ private String productSeoTitle; /** 产品页面属性关键字 */ private String productSeoKeyword; /** 产品页面属性描述 */ private String productSeoDescription; /** 后台产品配件说明 */ private String accessoryDescription; /** 是否需要单独开票 */ private Integer needInvoice; /** 清仓原因 */ private String clearCause; /** 默认商品条码ID */ private Long defaultBarcodeId; /** 广告词 */ private String adWord; /** 是否是3c产品(0:非3C,1:3C产品) */ private Integer isCcc; /** N件购 */ private Integer shoppingCount; /** 是否为赠品 */ private Integer productIsGift; /** 是否可以退换货 0:不可以 1:可以 */ private Integer canReturnAndChange; /** 是否需要检测 0:不需要 1:需要 */ private Integer needExamine; /** 1:新增未审核;2:编辑待审核;3:审核未通过;4:审核通过;5:文描审核失败;6:图片审核失败 */ private Integer verifyFlg; /** 审核者 */ private Long verifyBy; /** 审核日时 */ /** 商品登记者 */ private Long registerBy; /** 商品登记日时 */ /** 商品登记者联系电话 */ private String registerPhone; /** 审核备注 */ private String verifyRemark; /** 批量数 */ private Integer batchNum; /** 是否只限本地配送0: 不限制 1:限制 (粉状/液体/膏状) */ private Integer localLimit; /** 一包的数量 */ private Integer stdPackQty; /** 正式表产品ID */ private Long fromalProductId; /** 是否强制发票 */ private Integer isMustInvoice; /** 审核失败原因 */ private Integer verifyFailureType; /** 产品类型 0:普通产品 1:主系列产品 2:子系列产品 3:捆绑产品 4:礼品卡 5: 虚拟商品 6:增值服务 */ private Integer productType; /** 是否能被采购 */ private Integer canPurchase; /** 标准包装箱sku */ private String stdPackageSku; /** 是否需要启用保质期控制 0:不启用 1:启用 */ private Integer userExpireControl; /** 批次规则ID */ private Long batchRuleId; /** 产品名称副标题 */ private String nameSubtitle; private String specialType; /** 给经销商的批发价 */ private Double batchPrice; /** 是否需要批次控制 0:不需要 1:需要 */ private Integer needBatchControl; /** 销售税率 */ private Double salesTax; /** 外部产品编码 */ private String outerId; /** 商家ID */ private Long merchantId; /** 商家名称 */ private String merchantName; /** 商家产品主类别(用于报表统计) */ private Long masterCategoryId; private Integer concernLevel; /** 关注理由 */ private String concernReason; /** 是否可售 */ private Integer canSale; /** 是否显示 */ private Integer canShow; /** 产品销售税率 */ private Long prodcutTaxRate; /** 是否支持VIP0:不支持1:支持 */ private Integer canVipDiscount; /** 分类名称 */ private String categoryName; /** 销售价格 */ private Double salePrice; /** 库存 */ private Long stockNum; /** 商家类别名称 */ private String merchantCategoryName; /** 商家详情 */ private String productDescription; /** 是否可调拨 0:不可以 1:可以 */ private Integer isTransfer; /** 是否需要审核0:新增未提交;1:需要审核;2:编辑未提交 */ private Integer isSubmit; /** 审核失败类型 */ private Integer verifyFailueType; /** 产品拼音 */ private String productSpell; /** 产品名称前缀 */ private String productNamePrefix; /** 审核失败原因 */ private String failueReason; /** orgPicUrl */ private String orgPicUrl; /** 扩展分类名称 */ private String subCategoryName; /** 扩展分类ID */ private Long subCategoryId; /** 7天内日均销量 */ private Integer dailySale; /** 查看是否有主图 */ private Integer picCount; /** 强制下架原因 */ private Integer underCarriageReason; /** 强制下架原因-中文信息 */ private String underCarriageReasonStr; /** 异常信息 */ private String errorMessage; /** 库存预警数量 */ private Integer alertStockCount; private String deliveryInfo; /** 主图链接 */ private String picUrl; /** 是否能分期0:不能 1:能 */ private Integer canFenqi; private String season; /** 是否是二次审核 */ private Integer isDupAudit; private Integer viewFromTag; /** 产品售价 */ private Double productNonMemberPrice; /** 产品图片 */ /** 是否更新操作 */ private Integer isUpdate; /** merchantRpcVo */ /** 系列产品的颜色图片 */ /** 系列产品的尺码 */ private List productSizeSet; /** 是否主产品 */ private Boolean isMainProduct; /** 从图片空间中返回图片ID和URL */ private String productPicIdAndURL; private Integer isTemp; /** 市场价和售价的比例 */ private Double priceRate; private Integer picSpecialType; private Integer exemptStatus; private String violationReasonIds; private String violationReasons; private Long remainTime; private Integer [MASK] ; private Integer productSource; private Integer isKa; /** KA商家创建时间 */ private Integer kaMCreateTime; /** 配送延长期 */ private Integer deliveryDay; /** 产品状态 */ private Integer isEdit; /** 操作人 */ private Long backOperatorId; /** 正式库pm_info_id */ private Long formalPmInfoId; /** 类别拼接字符串 */ private String categoryStr; /** 类别id拼接字符串 */ private String categoryIdStr; /** 扩展类别拼接字符串 */ private String extendCategoryStr; /** 扩展类别id拼接字符串 */ private String extendCategoryIdStr; /** 商家类别ID */ private List masterCategoryIdList; private Long defaultWarehouseId; public Long getBackOperatorId() { return backOperatorId; } public void setBackOperatorId(Long backOperatorId) { this.backOperatorId = backOperatorId; } public Integer getIsDupAudit() { return isDupAudit; } public void setIsDupAudit(Integer isDupAudit) { this.isDupAudit = isDupAudit; } public Long getId() { return id; } public String getUnderCarriageReasonStr() { return underCarriageReasonStr; } public void setUnderCarriageReasonStr(String underCarriageReasonStr) { this.underCarriageReasonStr = underCarriageReasonStr; } /** * 产品ID * * @param id 产品ID */ public void setId(Long id) { this.id = id; } /** * 产品编码 * * @return productCode */ public String getProductCode() { return productCode; } /** * 产品编码 * * @param productCode 产品编码 */ public void setProductCode(String productCode) { this.productCode = productCode; } /** * 产品名 * * @return productCname */ public String getProductCname() { return productCname; } /** * 产品名 * * @param productCname 产品名 */ public void setProductCname(String productCname) { this.productCname = productCname; } /** * 产品名英文 * * @return productEname */ public String getProductEname() { return productEname; } /** * 产品名英文 * * @param productEname 产品名英文 */ public void setProductEname(String productEname) { this.productEname = productEname; } /** * 产品销售类别 * * @param productSaleType 产品销售类别 */ public void setProductSaleType(Integer productSaleType) { this.productSaleType = productSaleType; } /** * 产品品牌Id * * @return brandId */ public Long getBrandId() { return brandId; } /** * 产品品牌Id * * @param brandId 产品品牌Id */ public void setBrandId(Long brandId) { this.brandId = brandId; } /** * 产品品牌 * * @return brandName */ public String getBrandName() { return brandName; } /** * 产品品牌 * * @param brandName 产品品牌 */ public void setBrandName(String brandName) { this.brandName = brandName; } /** * 产品创建时间 * * @return createTime */ /** * 产品创建时间 * * @param createTime 产品创建时间 */ /** * 市场价 * * @return productListPrice */ public Double getProductListPrice() { return productListPrice; } /** * 市场价 * * @param productListPrice 市场价 */ public void setProductListPrice(Double productListPrice) { this.productListPrice = productListPrice; } /** * 分类Id * * @return categoryId */ public Long getCategoryId() { return categoryId; } /** * 分类Id * * @param categoryId 分类Id */ public void setCategoryId(Long categoryId) { this.categoryId = categoryId; } /** * 厂商ID默认为1 * * @return mfid */ public Long getMfid() { return mfid; } /** * 厂商ID默认为1 * * @param mfid 厂商ID默认为1 */ public void setMfid(Long mfid) { this.mfid = mfid; } /** * productCanBeChange * * @return productCanBeChange */ public Integer getProductCanBeChange() { return productCanBeChange; } /** * productCanBeChange * * @param productCanBeChange productCanBeChange */ public void setProductCanBeChange(Integer productCanBeChange) { this.productCanBeChange = productCanBeChange; } /** * productChangeDay * * @return productChangeDay */ public Integer getProductChangeDay() { return productChangeDay; } /** * productChangeDay * * @param productChangeDay productChangeDay */ public void setProductChangeDay(Integer productChangeDay) { this.productChangeDay = productChangeDay; } /** * productCanBeReturn * * @return productCanBeReturn */ public Integer getProductCanBeReturn() { return productCanBeReturn; } /** * productCanBeReturn * * @param productCanBeReturn productCanBeReturn */ public void setProductCanBeReturn(Integer productCanBeReturn) { this.productCanBeReturn = productCanBeReturn; } /** * productReturnDay * * @return productReturnDay */ public Integer getProductReturnDay() { return productReturnDay; } /** * productReturnDay * * @param productReturnDay productReturnDay */ public void setProductReturnDay(Integer productReturnDay) { this.productReturnDay = productReturnDay; } /** * 能否维修 * * @return productCanBeRepair */ public Integer getProductCanBeRepair() { return productCanBeRepair; } /** * 能否维修 * * @param productCanBeRepair 能否维修 */ public void setProductCanBeRepair(Integer productCanBeRepair) { this.productCanBeRepair = productCanBeRepair; } /** * 能否维修 * * @return productCanBeRepairDay */ public Integer getProductCanBeRepairDay() { return productCanBeRepairDay; } /** * 能否维修 * * @param productCanBeRepairDay 能否维修 */ public void setProductCanBeRepairDay(Integer productCanBeRepairDay) { this.productCanBeRepairDay = productCanBeRepairDay; } /** * 安装信息 * * @return productNeedInstallation */ public Integer getProductNeedInstallation() { return productNeedInstallation; } /** * 安装信息 * * @param productNeedInstallation 安装信息 */ public void setProductNeedInstallation(Integer productNeedInstallation) { this.productNeedInstallation = productNeedInstallation; } /** * 条形码 * * @return ean13 */ public String getEan13() { return ean13; } /** * 条形码 * * @param ean13 条形码 */ public void setEan13(String ean13) { this.ean13 = ean13; } /** * sku * * @return sku */ public String getSku() { return sku; } /** * sku * * @param sku sku */ public void setSku(String sku) { this.sku = sku; } /** * 长 * * @return length */ public Double getLength() { return length; } /** * 长 * * @param length 长 */ public void setLength(Double length) { this.length = length; } /** * 宽 * * @return width */ public Double getWidth() { return width; } /** * 宽 * * @param width 宽 */ public void setWidth(Double width) { this.width = width; } /** * 高 * * @return height */ public Double getHeight() { return height; } /** * 高 * * @param height 高 */ public void setHeight(Double height) { this.height = height; } /** * 净重 * * @return weight */ public Double getWeight() { return weight; } /** * 净重 * * @param weight 净重 */ public void setWeight(Double weight) { this.weight = weight; } /** * keepTemperature * * @return keepTemperature */ public String getKeepTemperature() { return keepTemperature; } /** * keepTemperature * * @param keepTemperature keepTemperature */ public void setKeepTemperature(String keepTemperature) { this.keepTemperature = keepTemperature; } /** * keepHumidity * * @return keepHumidity */ public String getKeepHumidity() { return keepHumidity; } /** * keepHumidity * * @param keepHumidity keepHumidity */ public void setKeepHumidity(String keepHumidity) { this.keepHumidity = keepHumidity; } /** * keepSpecCondition * * @return keepSpecCondition */ public String getKeepSpecCondition() { return keepSpecCondition; } /** * keepSpecCondition * * @param keepSpecCondition keepSpecCondition */ public void setKeepSpecCondition(String keepSpecCondition) { this.keepSpecCondition = keepSpecCondition; } /** * productQualityAssuranceDay * * @return productQualityAssuranceDay */ public Integer getProductQualityAssuranceDay() { return productQualityAssuranceDay; } /** * productQualityAssuranceDay * * @param productQualityAssuranceDay productQualityAssuranceDay */ public void setProductQualityAssuranceDay(Integer productQualityAssuranceDay) { this.productQualityAssuranceDay = productQualityAssuranceDay; } /** * 是否已删除 * * @return isDeleted */ public Integer getIsDeleted() { return isDeleted; } /** * 是否已删除 * * @param isDeleted 是否已删除 */ public void setIsDeleted(Integer isDeleted) { this.isDeleted = isDeleted; } /** * 单位 * * @return unit */ public String getUnit() { return unit; } /** * 单位 * * @param unit 单位 */ public void setUnit(String unit) { this.unit = unit; } /** * 进价 * * @return inPrice */ public Double getInPrice() { return inPrice; } /** * 进价 * * @param inPrice 进价 */ public void setInPrice(Double inPrice) { this.inPrice = inPrice; } /** * volume * * @return volume */ public Double getVolume() { return volume; } /** * volume * * @param volume volume */ public void setVolume(Double volume) { this.volume = volume; } /** * countryOfOrgn * * @return countryOfOrgn */ public Long getCountryOfOrgn() { return countryOfOrgn; } /** * countryOfOrgn * * @param countryOfOrgn countryOfOrgn */ public void setCountryOfOrgn(Long countryOfOrgn) { this.countryOfOrgn = countryOfOrgn; } /** * 主图ID * * @return defaultPictureId */ public Long getDefaultPictureId() { return defaultPictureId; } /** * 主图ID * * @param defaultPictureId 主图ID */ public void setDefaultPictureId(Long defaultPictureId) { this.defaultPictureId = defaultPictureId; } /** * 主图URL * * @return defaultPictureUrl */ public String getDefaultPictureUrl() { return defaultPictureUrl; } /** * 主图URL * * @param defaultPictureUrl 主图URL */ public void setDefaultPictureUrl(String defaultPictureUrl) { this.defaultPictureUrl = defaultPictureUrl; } /** * color * * @return color */ public String getColor() { return color; } /** * color * * @param color color */ public void setColor(String color) { this.color = color; } /** * currencyId * * @return currencyId */ public Long getCurrencyId() { return currencyId; } /** * currencyId * * @param currencyId currencyId */ public void setCurrencyId(Long currencyId) { this.currencyId = currencyId; } /** * 毛重 * * @return grossWeight */ public Double getGrossWeight() { return grossWeight; } /** * 毛重 * * @param grossWeight 毛重 */ public void setGrossWeight(Double grossWeight) { this.grossWeight = grossWeight; } /** * format * * @return format */ public String getFormat() { return format; } /** * format * * @param format format */ public void setFormat(String format) { this.format = format; } /** * 易碎品0不是1是 * * @return isFragile */ public String getIsFragile() { return isFragile; } /** * 易碎品0不是1是 * * @param isFragile 易碎品0不是1是 */ public void setIsFragile(String isFragile) { this.isFragile = isFragile; } /** * 向上0不是1是 * * @return putOnDirection */ public String getPutOnDirection() { return putOnDirection; } /** * 向上0不是1是 * * @param putOnDirection 向上0不是1是 */ public void setPutOnDirection(String putOnDirection) { this.putOnDirection = putOnDirection; } /** * 贵重品0不是1是 * * @return isValuables */ public String getIsValuables() { return isValuables; } /** * 贵重品0不是1是 * * @param isValuables 贵重品0不是1是 */ public void setIsValuables(String isValuables) { this.isValuables = isValuables; } /** * 液体0不是1是 * * @return isLiquid */ public String getIsLiquid() { return isLiquid; } /** * 液体0不是1是 * * @param isLiquid 液体0不是1是 */ public void setIsLiquid(String isLiquid) { this.isLiquid = isLiquid; } /** * 防交叉污染0不是1是 * * @return isCrossContamination */ public String getIsCrossContamination() { return isCrossContamination; } /** * 防交叉污染0不是1是 * * @param isCrossContamination 防交叉污染0不是1是 */ public void setIsCrossContamination(String isCrossContamination) { this.isCrossContamination = isCrossContamination; } /** * 16进制的颜色代码如#FF00AA * * @return colorNumber */ public String getColorNumber() { return colorNumber; } /** * 16进制的颜色代码如#FF00AA * * @param colorNumber 16进制的颜色代码如#FF00AA */ public void setColorNumber(String colorNumber) { this.colorNumber = colorNumber; } /** * 尺码 * * @return productSize */ public String getProductSize() { return productSize; } /** * 尺码 * * @param productSize 尺码 */ public void setProductSize(String productSize) { this.productSize = productSize; } /** * 销售技巧 * * @return saleSkill */ public String getSaleSkill() { return saleSkill; } /** * 销售技巧 * * @param saleSkill 销售技巧 */ public void setSaleSkill(String saleSkill) { this.saleSkill = saleSkill; } /** * 本商品作为赠品时的处理方法 * * @return dispositionInstruct */ public String getDispositionInstruct() { return dispositionInstruct; } /** * 本商品作为赠品时的处理方法 * * @param dispositionInstruct 本商品作为赠品时的处理方法 */ public void setDispositionInstruct(String dispositionInstruct) { this.dispositionInstruct = dispositionInstruct; } /** * 产地 * * @return placeOfOrigin */ public String getPlaceOfOrigin() { return placeOfOrigin; } /** * 产地 * * @param placeOfOrigin 产地 */ public void setPlaceOfOrigin(String placeOfOrigin) { this.placeOfOrigin = placeOfOrigin; } /** * 产品页面标题 * * @return productSeoTitle */ public String getProductSeoTitle() { return productSeoTitle; } /** * 产品页面标题 * * @param productSeoTitle 产品页面标题 */ public void setProductSeoTitle(String productSeoTitle) { this.productSeoTitle = productSeoTitle; } /** * 产品页面属性关键字 * * @return productSeoKeyword */ public String getProductSeoKeyword() { return productSeoKeyword; } /** * 产品页面属性关键字 * * @param productSeoKeyword 产品页面属性关键字 */ public void setProductSeoKeyword(String productSeoKeyword) { this.productSeoKeyword = productSeoKeyword; } /** * 产品页面属性描述 * * @return productSeoDescription */ public String getProductSeoDescription() { return productSeoDescription; } /** * 产品页面属性描述 * * @param productSeoDescription 产品页面属性描述 */ public void setProductSeoDescription(String productSeoDescription) { this.productSeoDescription = productSeoDescription; } /** * 后台产品配件说明 * * @return accessoryDescription */ public String getAccessoryDescription() { return accessoryDescription; } /** * 后台产品配件说明 * * @param accessoryDescription 后台产品配件说明 */ public void setAccessoryDescription(String accessoryDescription) { this.accessoryDescription = accessoryDescription; } /** * 是否需要单独开票 * * @return needInvoice */ public Integer getNeedInvoice() { return needInvoice; } /** * 是否需要单独开票 * * @param needInvoice 是否需要单独开票 */ public void setNeedInvoice(Integer needInvoice) { this.needInvoice = needInvoice; } /** * 清仓原因 * * @return clearCause */ public String getClearCause() { return clearCause; } /** * 清仓原因 * * @param clearCause 清仓原因 */ public void setClearCause(String clearCause) { this.clearCause = clearCause; } /** * 默认商品条码ID * * @return defaultBarcodeId */ public Long getDefaultBarcodeId() { return defaultBarcodeId; } /** * 默认商品条码ID * * @param defaultBarcodeId 默认商品条码ID */ public void setDefaultBarcodeId(Long defaultBarcodeId) { this.defaultBarcodeId = defaultBarcodeId; } /** * 广告词 * * @return adWord */ public String getAdWord() { return adWord; } /** * 广告词 * * @param adWord 广告词 */ public void setAdWord(String adWord) { this.adWord = adWord; } /** * 是否是3c产品(0:非3C1:3C产品) * * @return isCcc */ public Integer getIsCcc() { return isCcc; } /** * 是否是3c产品(0:非3C1:3C产品) * * @param isCcc 是否是3c产品(0:非3C1:3C产品) */ public void setIsCcc(Integer isCcc) { this.isCcc = isCcc; } /** * N件购 * * @return shoppingCount */ public Integer getShoppingCount() { return shoppingCount; } /** * N件购 * * @param shoppingCount N件购 */ public void setShoppingCount(Integer shoppingCount) { this.shoppingCount = shoppingCount; } /** * 是否为赠品 * * @return productIsGift */ public Integer getProductIsGift() { return productIsGift; } /** * 是否为赠品 * * @param productIsGift 是否为赠品 */ public void setProductIsGift(Integer productIsGift) { this.productIsGift = productIsGift; } /** * 是否可以退换货0:不可以1:可以 * * @return canReturnAndChange */ public Integer getCanReturnAndChange() { return canReturnAndChange; } /** * 是否可以退换货0:不可以1:可以 * * @param canReturnAndChange 是否可以退换货0:不可以1:可以 */ public void setCanReturnAndChange(Integer canReturnAndChange) { this.canReturnAndChange = canReturnAndChange; } /** * 是否需要检测0:不需要1:需要 * * @return needExamine */ public Integer getNeedExamine() { return needExamine; } /** * 是否需要检测0:不需要1:需要 * * @param needExamine 是否需要检测0:不需要1:需要 */ public void setNeedExamine(Integer needExamine) { this.needExamine = needExamine; } /** * 1:新增未审核;2:编辑待审核;3:审核未通过;4:审核通过;5:文描审核失败;6:图片审核失败 * * @return verifyFlg */ public Integer getVerifyFlg() { return verifyFlg; } /** * 1:新增未审核;2:编辑待审核;3:审核未通过;4:审核通过;5:文描审核失败;6:图片审核失败 * * @param verifyFlg 1:新增未审核;2:编辑待审核;3:审核未通过;4:审核通过;5:文描审核失败;6:图片审核失败 */ public void setVerifyFlg(Integer verifyFlg) { this.verifyFlg = verifyFlg; } /** * 审核者 * * @return verifyBy */ public Long getVerifyBy() { return verifyBy; } /** * 审核者 * * @param verifyBy 审核者 */ public void setVerifyBy(Long verifyBy) { this.verifyBy = verifyBy; } /** * 审核日时 * * @return verifyAt */ /** * 审核日时 * * @param verifyAt 审核日时 */ /** * 商品登记者 * * @return registerBy */ public Long getRegisterBy() { return registerBy; } /** * 商品登记者 * * @param registerBy 商品登记者 */ public void setRegisterBy(Long registerBy) { this.registerBy = registerBy; } /** * 商品登记日时 * * @return registerAt */ /** * 商品登记日时 * * @param registerAt 商品登记日时 */ /** * 商品登记者联系电话 * * @return registerPhone */ public String getRegisterPhone() { return registerPhone; } /** * 商品登记者联系电话 * * @param registerPhone 商品登记者联系电话 */ public void setRegisterPhone(String registerPhone) { this.registerPhone = registerPhone; } /** * 审核备注 * * @return verifyRemark */ public String getVerifyRemark() { return verifyRemark; } /** * 审核备注 * * @param verifyRemark 审核备注 */ public void setVerifyRemark(String verifyRemark) { this.verifyRemark = verifyRemark; } /** * 批量数 * * @return batchNum */ public Integer getBatchNum() { return batchNum; } /** * 批量数 * * @param batchNum 批量数 */ public void setBatchNum(Integer batchNum) { this.batchNum = batchNum; } /** * 是否只限本地配送0:不限制1:限制(粉状液体膏状) * * @return localLimit */ public Integer getLocalLimit() { return localLimit; } /** * 是否只限本地配送0:不限制1:限制(粉状液体膏状) * * @param localLimit 是否只限本地配送0:不限制1:限制(粉状液体膏状) */ public void setLocalLimit(Integer localLimit) { this.localLimit = localLimit; } /** * 一包的数量 * * @return stdPackQty */ public Integer getStdPackQty() { return stdPackQty; } /** * 一包的数量 * * @param stdPackQty 一包的数量 */ public void setStdPackQty(Integer stdPackQty) { this.stdPackQty = stdPackQty; } /** * 正式表产品ID * * @return fromalProductId */ public Long getFromalProductId() { return fromalProductId; } /** * 正式表产品ID * * @param fromalProductId 正式表产品ID */ public void setFromalProductId(Long fromalProductId) { this.fromalProductId = fromalProductId; } /** * 是否强制发票 * * @return isMustInvoice */ public Integer getIsMustInvoice() { return isMustInvoice; } /** * 是否强制发票 * * @param isMustInvoice 是否强制发票 */ public void setIsMustInvoice(Integer isMustInvoice) { this.isMustInvoice = isMustInvoice; } /** * 审核失败原因 * * @return verifyFailureType */ public Integer getVerifyFailureType() { return verifyFailureType; } /** * 审核失败原因 * * @param verifyFailureType 审核失败原因 */ public void setVerifyFailureType(Integer verifyFailureType) { this.verifyFailureType = verifyFailureType; } /** * 产品类型0:普通产品1:主系列产品2:子系列产品3:捆绑产品4:礼品卡5:虚拟商品6:增值服务 * * @return productType */ public Integer getProductType() { return productType; } /** * 产品类型0:普通产品1:主系列产品2:子系列产品3:捆绑产品4:礼品卡5:虚拟商品6:增值服务 * * @param productType 产品类型0:普通产品1:主系列产品2:子系列产品3:捆绑产品4:礼品卡5:虚拟商品6:增值服务 */ public void setProductType(Integer productType) { this.productType = productType; } /** * 是否能被采购 * * @return canPurchase */ public Integer getCanPurchase() { return canPurchase; } /** * 是否能被采购 * * @param canPurchase 是否能被采购 */ public void setCanPurchase(Integer canPurchase) { this.canPurchase = canPurchase; } /** * 标准包装箱sku * * @return stdPackageSku */ public String getStdPackageSku() { return stdPackageSku; } /** * 标准包装箱sku * * @param stdPackageSku 标准包装箱sku */ public void setStdPackageSku(String stdPackageSku) { this.stdPackageSku = stdPackageSku; } /** * 是否需要启用保质期控制0:不启用1:启用 * * @return userExpireControl */ public Integer getUserExpireControl() { return userExpireControl; } /** * 是否需要启用保质期控制0:不启用1:启用 * * @param userExpireControl 是否需要启用保质期控制0:不启用1:启用 */ public void setUserExpireControl(Integer userExpireControl) { this.userExpireControl = userExpireControl; } /** * 批次规则ID * * @return batchRuleId */ public Long getBatchRuleId() { return batchRuleId; } /** * 批次规则ID * * @param batchRuleId 批次规则ID */ public void setBatchRuleId(Long batchRuleId) { this.batchRuleId = batchRuleId; } /** * 产品名称副标题 * * @return nameSubtitle */ public String getNameSubtitle() { return nameSubtitle; } /** * 产品名称副标题 * * @param nameSubtitle 产品名称副标题 */ public void setNameSubtitle(String nameSubtitle) { this.nameSubtitle = nameSubtitle; } /** * 产品特殊类型:1:医药;11:药品;12器械;14-18:处方药;50:电子凭证 * * @return specialType */ public String getSpecialType() { return specialType; } /** * 产品特殊类型:1:医药;11:药品;12器械;14-18:处方药;50:电子凭证 * * @param specialType 产品特殊类型:1:医药;11:药品;12器械;14-18:处方药;50:电子凭证 */ public void setSpecialType(String specialType) { this.specialType = specialType; } /** * 给经销商的批发价 * * @return batchPrice */ public Double getBatchPrice() { return batchPrice; } /** * 给经销商的批发价 * * @param batchPrice 给经销商的批发价 */ public void setBatchPrice(Double batchPrice) { this.batchPrice = batchPrice; } /** * 是否需要批次控制0:不需要1:需要 * * @return needBatchControl */ public Integer getNeedBatchControl() { return needBatchControl; } /** * 是否需要批次控制0:不需要1:需要 * * @param needBatchControl 是否需要批次控制0:不需要1:需要 */ public void setNeedBatchControl(Integer needBatchControl) { this.needBatchControl = needBatchControl; } /** * 销售税率 * * @return salesTax */ public Double getSalesTax() { return salesTax; } /** * 销售税率 * * @param salesTax 销售税率 */ public void setSalesTax(Double salesTax) { this.salesTax = salesTax; } /** * 外部产品编码 * * @return outerId */ public String getOuterId() { return outerId; } /** * 外部产品编码 * * @param outerId 外部产品编码 */ public void setOuterId(String outerId) { this.outerId = outerId; } /** * 商家ID * * @return merchantId */ public Long getMerchantId() { return merchantId; } /** * 商家ID * * @param merchantId 商家ID */ public void setMerchantId(Long merchantId) { this.merchantId = merchantId; } /** * 商家名称 * * @return merchantName */ public String getMerchantName() { return merchantName; } /** * 商家名称 * * @param merchantName 商家名称 */ public void setMerchantName(String merchantName) { this.merchantName = merchantName; } /** * 商家产品主类别(用于报表统计) * * @return masterCategoryId */ public Long getMasterCategoryId() { return masterCategoryId; } /** * 商家产品主类别(用于报表统计) * * @param masterCategoryId 商家产品主类别(用于报表统计) */ public void setMasterCategoryId(Long masterCategoryId) { this.masterCategoryId = masterCategoryId; } /** * 关注等级设置 * * @return concernLevel */ public Integer getConcernLevel() { return concernLevel; } /** * 关注等级设置 * * @param concernLevel 关注等级设置 */ public void setConcernLevel(Integer concernLevel) { this.concernLevel = concernLevel; } /** * 关注理由 * * @return concernReason */ public String getConcernReason() { return concernReason; } /** * 关注理由 * * @param concernReason 关注理由 */ public void setConcernReason(String concernReason) { this.concernReason = concernReason; } /** * 是否可售 * * @return canSale */ public Integer getCanSale() { return canSale; } /** * 是否可售 * * @param canSale 是否可售 */ public void setCanSale(Integer canSale) { this.canSale = canSale; } /** * 是否显示 * * @return canShow */ public Integer getCanShow() { return canShow; } /** * 是否显示 * * @param canShow 是否显示 */ public void setCanShow(Integer canShow) { this.canShow = canShow; } /** * 产品销售税率 * * @return prodcutTaxRate */ public Long getProdcutTaxRate() { return prodcutTaxRate; } /** * 产品销售税率 * * @param prodcutTaxRate 产品销售税率 */ public void setProdcutTaxRate(Long prodcutTaxRate) { this.prodcutTaxRate = prodcutTaxRate; } /** * 是否支持VIP0:不支持1:支持 * * @return canVipDiscount */ public Integer getCanVipDiscount() { return canVipDiscount; } /** * 是否支持VIP0:不支持1:支持 * * @param canVipDiscount 是否支持VIP0:不支持1:支持 */ public void setCanVipDiscount(Integer canVipDiscount) { this.canVipDiscount = canVipDiscount; } /** * 分类名称 * * @return categoryName */ public String getCategoryName() { return categoryName; } /** * 分类名称 * * @param categoryName 分类名称 */ public void setCategoryName(String categoryName) { this.categoryName = categoryName; } /** * 销售价格 * * @return salePrice */ public Double getSalePrice() { return salePrice; } /** * 销售价格 * * @param salePrice 销售价格 */ public void setSalePrice(Double salePrice) { this.salePrice = salePrice; } /** * 库存 * * @return stockNum */ public Long getStockNum() { return stockNum; } /** * 库存 * * @param stockNum 库存 */ public void setStockNum(Long stockNum) { this.stockNum = stockNum; } /** * 商家类别名称 * * @return merchantCategoryName */ public String getMerchantCategoryName() { return merchantCategoryName; } /** * 商家类别名称 * * @param merchantCategoryName 商家类别名称 */ public void setMerchantCategoryName(String merchantCategoryName) { this.merchantCategoryName = merchantCategoryName; } /** * 商家详情 * * @return productDescription */ public String getProductDescription() { return productDescription; } /** * 商家详情 * * @param productDescription 商家详情 */ public void setProductDescription(String productDescription) { this.productDescription = productDescription; } /** * 是否可调拨0:不可以1:可以 * * @return isTransfer */ public Integer getIsTransfer() { return isTransfer; } /** * 是否可调拨0:不可以1:可以 * * @param isTransfer 是否可调拨0:不可以1:可以 */ public void setIsTransfer(Integer isTransfer) { this.isTransfer = isTransfer; } /** * 是否需要审核0:新增未提交;1:需要审核;2:编辑未提交 * * @return isSubmit */ public Integer getIsSubmit() { return isSubmit; } /** * 是否需要审核0:新增未提交;1:需要审核;2:编辑未提交 * * @param isSubmit 是否需要审核0:新增未提交;1:需要审核;2:编辑未提交 */ public void setIsSubmit(Integer isSubmit) { this.isSubmit = isSubmit; } /** * 审核失败类型 * * @return verifyFailueType */ public Integer getVerifyFailueType() { return verifyFailueType; } /** * 审核失败类型 * * @param verifyFailueType 审核失败类型 */ public void setVerifyFailueType(Integer verifyFailueType) { this.verifyFailueType = verifyFailueType; } /** * 产品拼音 * * @return productSpell */ public String getProductSpell() { return productSpell; } /** * 产品拼音 * * @param productSpell 产品拼音 */ public void setProductSpell(String productSpell) { this.productSpell = productSpell; } /** * 产品名称前缀 * * @return productNamePrefix */ public String getProductNamePrefix() { return productNamePrefix; } /** * 产品名称前缀 * * @param productNamePrefix 产品名称前缀 */ public void setProductNamePrefix(String productNamePrefix) { this.productNamePrefix = productNamePrefix; } /** * 审核失败原因 * * @return failueReason */ public String getFailueReason() { return failueReason; } /** * 审核失败原因 * * @param failueReason 审核失败原因 */ public void setFailueReason(String failueReason) { this.failueReason = failueReason; } /** * orgPicUrl * * @return orgPicUrl */ public String getOrgPicUrl() { return orgPicUrl; } /** * orgPicUrl * * @param orgPicUrl orgPicUrl */ public void setOrgPicUrl(String orgPicUrl) { this.orgPicUrl = orgPicUrl; } /** * 扩展分类名称 * * @return subCategoryName */ public String getSubCategoryName() { return subCategoryName; } /** * 扩展分类名称 * * @param subCategoryName 扩展分类名称 */ public void setSubCategoryName(String subCategoryName) { this.subCategoryName = subCategoryName; } /** * 扩展分类ID * * @return subCategoryId */ public Long getSubCategoryId() { return subCategoryId; } /** * 扩展分类ID * * @param subCategoryId 扩展分类ID */ public void setSubCategoryId(Long subCategoryId) { this.subCategoryId = subCategoryId; } /** * 7天内日均销量 * * @return dailySale */ public Integer getDailySale() { return dailySale; } /** * 7天内日均销量 * * @param dailySale 7天内日均销量 */ public void setDailySale(Integer dailySale) { this.dailySale = dailySale; } /** * 查看是否有主图 * * @return picCount */ public Integer getPicCount() { return picCount; } /** * 查看是否有主图 * * @param picCount 查看是否有主图 */ public void setPicCount(Integer picCount) { this.picCount = picCount; } /** * 强制下架原因 * * @return underCarriageReason */ public Integer getUnderCarriageReason() { return underCarriageReason; } /** * 强制下架原因 * * @param underCarriageReason 强制下架原因 */ public void setUnderCarriageReason(Integer underCarriageReason) { this.underCarriageReason = underCarriageReason; } /** * 异常信息 * * @return errorMessage */ public String getErrorMessage() { return errorMessage; } /** * 异常信息 * * @param errorMessage 异常信息 */ /** * public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } 库存预警数量 * * @return alertStockCount */ public Integer getAlertStockCount() { return alertStockCount; } /** * 库存预警数量 * * @param alertStockCount 库存预警数量 */ public void setAlertStockCount(Integer alertStockCount) { this.alertStockCount = alertStockCount; } /** * 提交时间 * * @return submitTime */ /** * public Date getSubmitTime() { return submitTime; } 提交时间 * * @param submitTime 提交时间 */ /** * public void setSubmitTime(Date submitTime) { this.submitTime = submitTime; } holdPmPriceRpcVo * * @return holdPmPriceRpcVo */ /** * holdPmPriceRpcVo * * @param holdPmPrice holdPmPriceRpcVo */ /** * pmPriceRpcVo * * @return pmPriceRpcVo */ /** * public PmPriceRpcVo getPmPrice() { return pmPrice; } pmPriceRpcVo * * @param pmPrice pmPriceRpcVo public void setPmPrice(PmPriceRpcVo pmPrice) { this.pmPrice = pmPrice; } */ public Long getFormalPmInfoId() { return formalPmInfoId; } public void setFormalPmInfoId(Long formalPmInfoId) { this.formalPmInfoId = formalPmInfoId; } /** * 库存状况(产品预览页用) * * @return deliveryInfo */ public String getDeliveryInfo() { return deliveryInfo; } /** * 库存状况(产品预览页用) * * @param deliveryInfo 库存状况(产品预览页用) */ public void setDeliveryInfo(String deliveryInfo) { this.deliveryInfo = deliveryInfo; } /** * 主图链接 * * @return picUrl */ public String getPicUrl() { return picUrl; } /** * 主图链接 * * @param picUrl 主图链接 */ public void setPicUrl(String picUrl) { this.picUrl = picUrl; } /** * 跳到商品详情页的来源0:首次审核页面1:二次审核页面2:审核失败页面 * * @return viewFromTag */ public Integer getViewFromTag() { return viewFromTag; } /** * 跳到商品详情页的来源0:首次审核页面1:二次审核页面2:审核失败页面 * * @param viewFromTag 跳到商品详情页的来源0:首次审核页面1:二次审核页面2:审核失败页面 */ public void setViewFromTag(Integer viewFromTag) { this.viewFromTag = viewFromTag; } public Double getProductNonMemberPrice() { return productNonMemberPrice; } /** * 产品售价 * * @param productNonMemberPrice 产品售价 */ public void setProductNonMemberPrice(Double productNonMemberPrice) { this.productNonMemberPrice = productNonMemberPrice; } public Integer getIsUpdate() { return isUpdate; } /** * 是否更新操作 * * @param isUpdate 是否更新操作 */ public void setIsUpdate(Integer isUpdate) { this.isUpdate = isUpdate; } public List getProductSizeSet() { return productSizeSet; } public void setProductSizeSet(List productSizeSet) { this.productSizeSet = productSizeSet; } public Boolean getIsMainProduct() { return isMainProduct; } /** * 是否主产品 * * @param isMainProduct 是否主产品 */ public void setIsMainProduct(Boolean isMainProduct) { this.isMainProduct = isMainProduct; } /** * 从图片空间中返回图片ID和URL * * @return productPicIdAndURL */ public String getProductPicIdAndURL() { return productPicIdAndURL; } /** * 从图片空间中返回图片ID和URL * * @param productPicIdAndURL 从图片空间中返回图片ID和URL */ public void setProductPicIdAndURL(String productPicIdAndURL) { this.productPicIdAndURL = productPicIdAndURL; } public Integer getIsTemp() { return isTemp; } /** * isTemp * * @param isTemp isTemp */ public void setIsTemp(Integer isTemp) { this.isTemp = isTemp; } public Double getPriceRate() { return priceRate; } public void setPriceRate(Double priceRate) { this.priceRate = priceRate; } public Integer getPicSpecialType() { return picSpecialType; } public void setPicSpecialType(Integer picSpecialType) { this.picSpecialType = picSpecialType; } public Integer getExemptStatus() { return exemptStatus; } public void setExemptStatus(Integer exemptStatus) { this.exemptStatus = exemptStatus; } public String getViolationReasonIds() { return violationReasonIds; } /** * 免审商家新增字段:记录违规的原因 * * @param violationReasonIds 免审商家新增字段:记录违规的原因 */ public void setViolationReasonIds(String violationReasonIds) { this.violationReasonIds = violationReasonIds; } /** * 免审商家新增字段:记录违规的原因文字信息,逗号分隔 * * @return violationReasons */ public String getViolationReasons() { return violationReasons; } public void setViolationReasons(String violationReasons) { this.violationReasons = violationReasons; } /** * 违规限定修改剩余时间(毫秒数) * * @return remainTime */ public Long getRemainTime() { return remainTime; } /** * 违规限定修改剩余时间(毫秒数) * * @param remainTime 违规限定修改剩余时间(毫秒数) */ public void setRemainTime(Long remainTime) { this.remainTime = remainTime; } public Integer getSubmitOrder() { return [MASK] ; } public void setSubmitOrder(Integer [MASK] ) { this. [MASK] = [MASK] ; } public Integer getProductSource() { return productSource; } public void setProductSource(Integer productSource) { this.productSource = productSource; } public String getProductSname() { return productSname; } public void setProductSname(String productSname) { this.productSname = productSname; } public Integer getCanFenqi() { return canFenqi; } public void setCanFenqi(Integer canFenqi) { this.canFenqi = canFenqi; } public String getSeason() { return season; } public void setSeason(String season) { this.season = season; } public Integer getIsKa() { return isKa; } public void setIsKa(Integer isKa) { this.isKa = isKa; } public Integer getKaMCreateTime() { return kaMCreateTime; } public void setKaMCreateTime(Integer kaMCreateTime) { this.kaMCreateTime = kaMCreateTime; } public Integer getDeliveryDay() { return deliveryDay; } public void setDeliveryDay(Integer deliveryDay) { this.deliveryDay = deliveryDay; } public Integer getIsEdit() { return isEdit; } public void setIsEdit(Integer isEdit) { this.isEdit = isEdit; } public String getProductBrandName() { return productBrandName; } public void setProductBrandName(String productBrandName) { this.productBrandName = productBrandName; } /** * 类别拼接字符串 * * @return categoryStr */ public String getCategoryStr() { return categoryStr; } /** * 类别拼接字符串 * * @param categoryStr 类别拼接字符串 */ public void setCategoryStr(String categoryStr) { this.categoryStr = categoryStr; } /** * 扩展类别拼接字符串 * * @return extendCategoryStr */ public String getExtendCategoryStr() { return extendCategoryStr; } /** * 扩展类别拼接字符串 * * @param extendCategoryStr 扩展类别拼接字符串 */ public void setExtendCategoryStr(String extendCategoryStr) { this.extendCategoryStr = extendCategoryStr; } public String getCategoryIdStr() { return categoryIdStr; } public void setCategoryIdStr(String categoryIdStr) { this.categoryIdStr = categoryIdStr; } public String getExtendCategoryIdStr() { return extendCategoryIdStr; } public Long getDefaultWarehouseId() { return defaultWarehouseId; } public void setDefaultWarehouseId(Long defaultWarehouseId) { this.defaultWarehouseId = defaultWarehouseId; } public Long getOldCategoryId() { return oldCategoryId; } public void setOldCategoryId(Long oldCategoryId) { this.oldCategoryId = oldCategoryId; } public Long getOldExtendCategoryId() { return oldExtendCategoryId; } public void setOldExtendCategoryId(Long oldExtendCategoryId) { this.oldExtendCategoryId = oldExtendCategoryId; } public String getDeletedProductId() { return deletedProductId; } public void setDeletedProductId(String deletedProductId) { this.deletedProductId = deletedProductId; } public String getReplaceProductSize() { return replaceProductSize; } public void setReplaceProductSize(String replaceProductSize) { this.replaceProductSize = replaceProductSize; } public List getMasterCategoryIdList() { return masterCategoryIdList; } public void setMasterCategoryIdList(List masterCategoryIdList) { //this.masterCategoryIdList = masterCategoryIdList; } } } ","submitOrder " "// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Streams.concat; import static java.util.stream.Collectors.toList; import com.android.builder.core.VariantTypeImpl; import com.android.utils.StdLogger; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.devtools.build.android.Converters.DependencyAndroidDataListConverter; import com.google.devtools.build.android.Converters.PathConverter; import com.google.devtools.build.android.Converters.PathListConverter; import com.google.devtools.build.android.Converters.SerializedAndroidDataListConverter; import com.google.devtools.build.android.Converters.UnvalidatedAndroidDataConverter; import com.google.devtools.build.android.Converters.VariantTypeConverter; import com.google.devtools.build.android.aapt2.Aapt2ConfigOptions; import com.google.devtools.build.android.aapt2.CompiledResources; import com.google.devtools.build.android.aapt2.PackagedResources; import com.google.devtools.build.android.aapt2.ProtoApk; import com.google.devtools.build.android.aapt2.ResourceCompiler; import com.google.devtools.build.android.aapt2.ResourceLinker; import com.google.devtools.build.android.aapt2.StaticLibrary; import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor; import com.google.devtools.common.options.TriState; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; /** * Provides an entry point for the resource processing using the AOSP build tools. * *

 * Example Usage:
 *   java/com/google/build/android/Aapt2ResourcePackagingAction\
 *      --sdkRoot path/to/sdk\
 *      --aapt path/to/sdk/aapt\
 *      --adb path/to/sdk/adb\
 *      --zipAlign path/to/sdk/zipAlign\
 *      --androidJar path/to/sdk/androidJar\
 *      --manifestOutput path/to/manifest\
 *      --primaryData path/to/resources:path/to/assets:path/to/manifest\
 *      --data p/t/res1:p/t/assets1:p/t/1/AndroidManifest.xml:p/t/1/R.txt:symbols,\
 *             p/t/res2:p/t/assets2:p/t/2/AndroidManifest.xml:p/t/2/R.txt:symbols\
 *      --packagePath path/to/write/archive.ap_\
 *      --srcJarOutput path/to/write/archive.srcjar
 * 
*/ public class Aapt2ResourcePackagingAction { private static final StdLogger STD_LOGGER = new StdLogger(StdLogger.Level.WARNING); /** Flag specifications for this action. */ public static final class Options extends OptionsBase { @Option( name = ""primaryData"", defaultValue = ""null"", converter = UnvalidatedAndroidDataConverter.class, category = ""input"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""The directory containing the primary resource directory. The contents will override "" + ""the contents of any other resource directories during merging. The expected "" + ""format is "" + UnvalidatedAndroidData.EXPECTED_FORMAT) public UnvalidatedAndroidData primaryData; @Option( name = ""data"", defaultValue = """", converter = DependencyAndroidDataListConverter.class, category = ""input"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Transitive Data dependencies. These values will be used if not defined in the "" + ""primary resources. The expected format is "" + DependencyAndroidData.EXPECTED_FORMAT + ""[,...]"") public List transitiveData; @Option( name = ""directData"", defaultValue = """", converter = DependencyAndroidDataListConverter.class, category = ""input"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Direct Data dependencies. These values will be used if not defined in the "" + ""primary resources. The expected format is "" + DependencyAndroidData.EXPECTED_FORMAT + ""[,...]"") public List directData; @Option( name = ""assets"", defaultValue = """", converter = SerializedAndroidDataListConverter.class, category = ""input"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Transitive asset dependencies. These can also be specified together with resources"" + "" using --data. Expected format: "" + SerializedAndroidData.EXPECTED_FORMAT + ""[,...]"") public List transitiveAssets; @Option( name = ""directAssets"", defaultValue = """", converter = SerializedAndroidDataListConverter.class, category = ""input"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Direct asset dependencies. These can also be specified together with resources using "" + ""--directData. Expected format: "" + SerializedAndroidData.EXPECTED_FORMAT + ""[,...]"") public List directAssets; @Option( name = ""additionalApksToLinkAgainst"", defaultValue = ""null"", category = ""input"", converter = PathListConverter.class, documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""List of APKs used during linking."") public List additionalApksToLinkAgainst; @Option( name = ""packageId"", defaultValue = ""-1"", category = ""input"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Resource ID prefix; see AAPT2 documentation for --package-id."") public int packageId; @Option( name = ""rOutput"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path to where the R.txt should be written."") public Path rOutput; @Option( name = ""symbolsOut"", oldName = ""symbolsTxtOut"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path to where the symbols should be written."") public Path symbolsOut; @Option( name = ""dataBindingInfoOut"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path to where data binding's layout info output should be written."") public Path dataBindingInfoOut; @Option( name = ""packagePath"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path to the write the archive."") public Path packagePath; @Option( name = ""resourcesOutput"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path to the write merged resources archive."") public Path resourcesOutput; @Option( name = ""proguardOutput"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path for the proguard file."") public Path proguardOutput; @Option( name = ""mainDexProguardOutput"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path for the main dex proguard file."") public Path mainDexProguardOutput; @Option( name = ""manifestOutput"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path for the modified manifest."") public Path manifestOutput; @Option( name = ""srcJarOutput"", defaultValue = ""null"", converter = PathConverter.class, category = ""output"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Path for the generated java source jar."") public Path srcJarOutput; @Option( name = ""packageType"", defaultValue = ""BASE_APK"", converter = VariantTypeConverter.class, category = ""config"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Variant configuration type for packaging the resources."" + "" Acceptable values BASE_APK, LIBRARY, ANDROID_TEST, UNIT_TEST"") public VariantTypeImpl packageType; @Option( name = ""densities"", defaultValue = """", converter = CommaSeparatedOptionListConverter.class, category = ""config"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""A list of densities to filter the resource drawables by."") public List densities; @Option( name = ""packageForR"", defaultValue = ""null"", category = ""config"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Custom java package to generate the R symbols files."") public String packageForR; @Option( name = ""applicationId"", defaultValue = ""null"", category = ""config"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Custom application id (package manifest) for the packaged manifest."") public String applicationId; @Option( name = ""versionName"", defaultValue = ""null"", category = ""config"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Version name to stamp into the packaged manifest."") public String versionName; @Option( name = ""versionCode"", defaultValue = ""-1"", category = ""config"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""Version code to stamp into the packaged manifest."") public int versionCode; @Option( name = ""throwOnResourceConflict"", defaultValue = ""false"", category = ""config"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""If passed, resource merge conflicts will be treated as errors instead of warnings"") public boolean throwOnResourceConflict; @Option( name = ""packageUnderTest"", defaultValue = ""null"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.NO_OP}, help = ""Unused/deprecated option."") public String packageUnderTest; @Option( name = ""isTestWithResources"", defaultValue = ""false"", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.NO_OP}, help = ""Unused/deprecated option."") public boolean isTestWithResources; @Option( name = ""includeProguardLocationReferences"", defaultValue = ""false"", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, help = ""When generating proguard configurations, include location references."") public boolean includeProguardLocationReferences; @Option( name = ""resourceApks"", defaultValue = ""null"", category = ""input"", converter = PathListConverter.class, documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, help = ""List of reource only APK files to link against."") public List resourceApks; } public static void main(String[] args) throws Exception { Profiler profiler = InMemoryProfiler.createAndStart(""setup""); OptionsParser optionsParser = OptionsParser.builder() .optionsClasses( Options.class, Aapt2ConfigOptions.class, ResourceProcessorCommonOptions.class) .argsPreProcessor(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault())) .build(); optionsParser.parseAndExitUponError(args); Aapt2ConfigOptions aaptConfigOptions = optionsParser.getOptions(Aapt2ConfigOptions.class); Options options = optionsParser.getOptions(Options.class); Preconditions.checkArgument( options.packageId == -1 || (options.packageId >= 2 && options.packageId <= 255), ""packageId must be in the range [2,255]""); try (ScopedTemporaryDirectory scopedTmp = new ScopedTemporaryDirectory(""android_resources_tmp""); ExecutorServiceCloser executorService = ExecutorServiceCloser.createWithFixedPoolOf(15)) { final Path tmp = scopedTmp.getPath(); final Path densityManifest = tmp.resolve(""manifest-filtered/AndroidManifest.xml""); final Path processedManifest = tmp.resolve(""manifest-processed/AndroidManifest.xml""); final Path symbols = tmp.resolve(""symbols/symbols.bin""); final Path databindingResourcesRoot = Files.createDirectories(tmp.resolve(""android_data_binding_resources"")); final Path [MASK] Resources = Files.createDirectories(tmp.resolve("" [MASK] "")); final Path linkedOut = Files.createDirectories(tmp.resolve(""linked"")); final AndroidCompiledDataDeserializer dataDeserializer = AndroidCompiledDataDeserializer.create(/*includeFileContentsForValidation=*/ true); final ResourceCompiler compiler = ResourceCompiler.create( executorService, [MASK] Resources, aaptConfigOptions.aapt2, aaptConfigOptions.buildToolsVersion, aaptConfigOptions.generatePseudoLocale); profiler.recordEndOf(""setup"").startTask(""compile""); CompiledResources [MASK] = options .primaryData .processDataBindings( options.dataBindingInfoOut, options.packageForR, databindingResourcesRoot, aaptConfigOptions.useDataBindingAndroidX) .compile(compiler, [MASK] Resources) .processManifest( manifest -> AndroidManifestProcessor.with(STD_LOGGER) .processManifest( options.applicationId, options.versionCode, options.versionName, manifest, processedManifest, optionsParser.getOptions(ResourceProcessorCommonOptions.class) .logWarnings)) .processManifest( manifest -> new DensitySpecificManifestProcessor(options.densities, densityManifest) .process(manifest)); profiler.recordEndOf(""compile"").startTask(""merge""); // Checks for merge conflicts, and write the merged data out. final Path symbolsBin = AndroidResourceMerger.mergeDataToSymbols( ParsedAndroidData.loadedFrom( DependencyInfo.DependencyType.PRIMARY, ImmutableList.of(SerializedAndroidData.from( [MASK] )), executorService, dataDeserializer), new DensitySpecificManifestProcessor(options.densities, densityManifest) .process(options.primaryData.getManifest()), ImmutableList.builder() .addAll(options.directData) .addAll(options.directAssets) .build(), ImmutableList.builder() .addAll(options.transitiveData) .addAll(options.transitiveAssets) .build(), options.packageType, symbols, dataDeserializer, options.throwOnResourceConflict, executorService); if (options.symbolsOut != null) { Files.copy(symbolsBin, options.symbolsOut); } profiler.recordEndOf(""merge"").startTask(""link""); // Write manifestOutput now before the dummy manifest is created. if (options.manifestOutput != null) { AndroidResourceOutputs.copyManifestToOutput( [MASK] , options.manifestOutput); } List [MASK] ResourceDeps = // Last defined dependencies will overwrite previous one, so always place direct // after transitive. concat(options.transitiveData.stream(), options.directData.stream()) .map(DependencyAndroidData::getCompiledSymbols) .collect(toList()); // NB: ""-A"" options are in *decreasing* precedence, while ""-R"" options are in *increasing* // precedence. While this is internally inconsistent, it matches AAPTv1's treatment of ""-A"". List assetDirs = concat( options.primaryData.assetDirs.stream(), concat( options.directData.stream(), options.directAssets.stream(), options.transitiveData.stream(), options.transitiveAssets.stream()) .flatMap(dep -> dep.assetDirs.stream())) .collect(toList()); List dependencies = Lists.newArrayList(StaticLibrary.from(aaptConfigOptions.androidJar)); if (options.additionalApksToLinkAgainst != null) { dependencies.addAll( options.additionalApksToLinkAgainst.stream() .map(StaticLibrary::from) .collect(toList())); } ImmutableList resourceApks = ImmutableList.of(); if (options.resourceApks != null) { resourceApks = options.resourceApks.stream().map(StaticLibrary::from).collect(toImmutableList()); } final PackagedResources packagedResources = ResourceLinker.create(aaptConfigOptions.aapt2, executorService, linkedOut) .profileUsing(profiler) .customPackage(options.packageForR) .packageId( options.packageId != -1 ? Optional.of(options.packageId) : Optional.empty()) .outputAsProto(aaptConfigOptions.resourceTableAsProto) .dependencies(ImmutableList.copyOf(dependencies)) .resourceApks(resourceApks) .include( [MASK] ResourceDeps) .withAssets(assetDirs) .buildVersion(aaptConfigOptions.buildToolsVersion) .conditionalKeepRules(aaptConfigOptions.conditionalKeepRules == TriState.YES) .filterToDensity(options.densities) .storeUncompressed(aaptConfigOptions.uncompressedExtensions) .debug(aaptConfigOptions.debug) .includeGeneratedLocales(aaptConfigOptions.generatePseudoLocale) .includeOnlyConfigs(aaptConfigOptions.resourceConfigs) .includeProguardLocationReferences(options.includeProguardLocationReferences) .link( [MASK] ); profiler.recordEndOf(""link"").startTask(""validate""); ValidateAndLinkResourcesAction.checkVisibilityOfResourceReferences( ProtoApk.readFrom(packagedResources.proto()).getManifest(), [MASK] , [MASK] ResourceDeps); profiler.recordEndOf(""validate""); if (options.packagePath != null) { copy(packagedResources.apk(), options.packagePath); } if (options.proguardOutput != null) { copy(packagedResources.proguardConfig(), options.proguardOutput); } if (options.mainDexProguardOutput != null) { copy(packagedResources.mainDexProguard(), options.mainDexProguardOutput); } if (options.srcJarOutput != null) { AndroidResourceOutputs.createSrcJar( packagedResources.javaSourceDirectory(), options.srcJarOutput, /* staticIds= */ false); } if (options.rOutput != null) { copy(packagedResources.rTxt(), options.rOutput); } if (options.resourcesOutput != null) { packagedResources.asArchive().writeTo(options.resourcesOutput, /* compress= */ false); } } } private static void copy(Path from, Path out) throws IOException { Files.createDirectories(out.getParent()); Files.copy(from, out); } } ","compiled " "/* * Copyright 2014 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.realm; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static io.realm.TestHelper.testNoObjectFound; import static io.realm.TestHelper.testOneObjectFound; import static io.realm.internal.test.ExtraTests.assertArrayEquals; import android.content.Context; import android.os.Build; import android.os.Looper; import android.os.StrictMode; import android.os.SystemClock; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.UiThreadTestRule; import junit.framework.AssertionFailedError; import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.hamcrest.CoreMatchers; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Random; import java.util.Scanner; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.Cat; import io.realm.entities.CyclicType; import io.realm.entities.CyclicTypePrimaryKey; import io.realm.entities.DefaultValueConstructor; import io.realm.entities.DefaultValueFromOtherConstructor; import io.realm.entities.DefaultValueOfField; import io.realm.entities.DefaultValueOverwriteNullLink; import io.realm.entities.DefaultValueSetter; import io.realm.entities.Dog; import io.realm.entities.DogPrimaryKey; import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.Object4957; import io.realm.entities.Owner; import io.realm.entities.OwnerPrimaryKey; import io.realm.entities.PrimaryKeyAsBoxedByte; import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsLong; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.PrimaryKeyMix; import io.realm.entities.PrimaryKeyRequiredAsBoxedByte; import io.realm.entities.PrimaryKeyRequiredAsBoxedInteger; import io.realm.entities.PrimaryKeyRequiredAsBoxedLong; import io.realm.entities.PrimaryKeyRequiredAsBoxedShort; import io.realm.entities.PrimaryKeyRequiredAsString; import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringAndInt; import io.realm.entities.StringOnly; import io.realm.entities.StringOnlyReadOnly; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.OsSharedRealm; import io.realm.internal.util.Pair; import io.realm.log.RealmLog; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.BlockingLooperThread; import io.realm.util.RealmThread; @RunWith(AndroidJUnit4.class) public class RealmTests { private final static int TEST_DATA_SIZE = 10; @Rule public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final TemporaryFolder tmpFolder = new TemporaryFolder(); @Rule public final ExpectedException thrown = ExpectedException.none(); public final BlockingLooperThread looperThread = new BlockingLooperThread(); private Context context; private Realm realm; private List columnData = new ArrayList() {{ add(AllTypes.FIELD_DOUBLE); add(AllTypes.FIELD_FLOAT); add(AllTypes.FIELD_LONG); add(AllTypes.FIELD_DECIMAL128); add(AllTypes.FIELD_BOOLEAN); add(AllTypes.FIELD_DATE); add(AllTypes.FIELD_OBJECT_ID); add(AllTypes.FIELD_STRING); add(AllTypes.FIELD_BINARY); add(AllTypes.FIELD_UUID); add(AllTypes.FIELD_REALM_ANY); }}; private RealmConfiguration realmConfig; @Before public void setUp() { // Injecting the Instrumentation instance is required // for your test to run with AndroidJUnitRunner. context = InstrumentationRegistry.getInstrumentation().getContext(); realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); } @After public void tearDown() { if (realm != null) { realm.close(); } } private void populateTestRealm(Realm realm, int objects) { realm.beginTransaction(); realm.deleteAll(); for (int i = 0; i < objects; ++i) { AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDate(new Date()); allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.234567F + i); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(i))); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(i + ""12345""))); allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(i))); allTypes.setColumnRealmAny(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(i)))); allTypes.setColumnString(""test data "" + i); allTypes.setColumnLong(i); NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); nonLatinFieldNames.set베타(1.234567F + i); nonLatinFieldNames.setΒήτα(1.234567F + i); } realm.commitTransaction(); } private void populateTestRealm() { populateTestRealm(realm, TEST_DATA_SIZE); } @Test public void getInstance_writeProtectedFile() throws IOException { String REALM_FILE = ""readonly.realm""; File folder = configFactory.getRoot(); File realmFile = new File(folder, REALM_FILE); assertFalse(realmFile.exists()); assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); try { Realm.getInstance(configFactory.createConfigurationBuilder() .directory(folder) .name(REALM_FILE) .build()); fail(); } catch (RealmFileException expected) { assertEquals(RealmFileException.Kind.PERMISSION_DENIED, expected.getKind()); } } @Test public void getInstance_writeProtectedFileWithContext() throws IOException { String REALM_FILE = ""readonly.realm""; File folder = configFactory.getRoot(); File realmFile = new File(folder, REALM_FILE); assertFalse(realmFile.exists()); assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); try { Realm.getInstance(configFactory.createConfigurationBuilder().directory(folder).name(REALM_FILE).build()); fail(); } catch (RealmFileException expected) { assertEquals(RealmFileException.Kind.PERMISSION_DENIED, expected.getKind()); } } @Test public void getInstance_twiceWhenRxJavaUnavailable() { // Test for https://github.com/realm/realm-java/issues/2416 // Though it's not a recommended way to create multiple configuration instance with the same parameter, it's legal. final RealmConfiguration configuration1 = configFactory.createConfiguration(""no_RxJava.realm""); TestHelper.emulateRxJavaUnavailable(configuration1); final RealmConfiguration configuration2 = configFactory.createConfiguration(""no_RxJava.realm""); TestHelper.emulateRxJavaUnavailable(configuration2); final Realm realm1 = Realm.getInstance(configuration1); //noinspection TryFinallyCanBeTryWithResources try { final Realm realm2 = Realm.getInstance(configuration2); realm2.close(); } finally { realm1.close(); } } @Test public void checkIfValid() { // checkIfValid() must not throw any Exception against valid Realm instance. realm.checkIfValid(); realm.close(); try { realm.checkIfValid(); fail(""closed Realm instance must throw IllegalStateException.""); } catch (IllegalStateException ignored) { } realm = null; } @Test public void getInstance() { assertNotNull(""Realm.getInstance unexpectedly returns null"", realm); assertTrue(""Realm.getInstance does not contain expected table"", realm.getSchema().contains(AllTypes.CLASS_NAME)); } @Test public void where() { populateTestRealm(); RealmResults resultList = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE, resultList.size()); } @Test public void where_throwsIfClassArgIsNotASubtype() { try { realm.where(RealmObject.class); fail(); } catch (IllegalArgumentException ignore) { } try { realm.where(RealmModel.class); fail(); } catch (IllegalArgumentException ignore) { } } // Note that this test is relying on the values set while initializing the test dataset // TODO Move to RealmQueryTests? @Test public void where_queryResults() throws IOException { populateTestRealm(realm, 159); RealmResults resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 33).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 3333).findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""test data 0"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""test data 0"", Case.INSENSITIVE).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""Test data 0"", Case.SENSITIVE).findAll(); assertEquals(0, resultList.size()); } // TODO Move to RealmQueryTests? @Test public void where_equalTo_wrongFieldTypeAsInput() throws IOException { populateTestRealm(); for (int i = 0; i < columnData.size(); i++) { // Realm queries applies coercion on numerical values boolean NON_NUMERICAL_COLUMN = (i > 4) && (i != 10); // Realm queries applies coercion on objectid and date boolean NON_OBJECT_OR_DATE = ((i <= 4) || (i > 6)) && (i != 10); // Realm queries applies coercion on string and binary boolean NON_STRING_OR_BINARY = ((i <= 6) || (i > 8)) && (i != 10); try { realm.where(AllTypes.class).equalTo(columnData.get(i), 13.37D).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), 13.3711F).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), 1337).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new Decimal128(new BigDecimal(i + ""12345""))).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), true).findAll(); if (NON_NUMERICAL_COLUMN) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new Date()).findAll(); if (NON_OBJECT_OR_DATE) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new ObjectId(TestHelper.generateObjectIdHexString(i))).findAll(); if (NON_OBJECT_OR_DATE) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), ""test"").findAll(); if (NON_STRING_OR_BINARY) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new byte[] {1, 2, 3}).findAll(); if (NON_STRING_OR_BINARY) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), UUID.fromString(TestHelper.generateUUIDString(i))).findAll(); if ((i != 9) && (i != 10)) { fail(""Realm.where should fail with illegal argument""); } } catch (IllegalArgumentException ignored) { } } } // TODO Move to RealmQueryTests? @Test public void where_equalTo_invalidFieldName() throws IOException { try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", 33).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", ""test"").findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", true).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", Math.PI).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo(""invalidcolumnname"", Math.PI).findAll(); fail(""Invalid field name""); } catch (Exception ignored) { } } @Test public void beginTransaction() throws IOException { populateTestRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnFloat(3.14F); allTypes.setColumnString(""a unique string""); realm.commitTransaction(); RealmResults resultList = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE + 1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, ""a unique string"").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 3.14F).findAll(); assertEquals(1, resultList.size()); } @Test public void nestedTransaction() { realm.beginTransaction(); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertTrue(e.getMessage().startsWith(""The Realm is already in a write transaction"")); } realm.commitTransaction(); } private enum Method { METHOD_BEGIN, METHOD_COMMIT, METHOD_CANCEL, METHOD_EXECUTE_TRANSACTION, METHOD_EXECUTE_TRANSACTION_ASYNC, METHOD_DELETE_TYPE, METHOD_DELETE_ALL, METHOD_CREATE_OBJECT, METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY, METHOD_COPY_TO_REALM, METHOD_COPY_TO_REALM_OR_UPDATE, METHOD_CREATE_ALL_FROM_JSON, METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON, METHOD_CREATE_FROM_JSON, METHOD_CREATE_OR_UPDATE_FROM_JSON, METHOD_INSERT_COLLECTION, METHOD_INSERT_OBJECT, METHOD_INSERT_OR_UPDATE_COLLECTION, METHOD_INSERT_OR_UPDATE_OBJECT } // Calling methods on a wrong thread will fail. private boolean runMethodOnWrongThread(final Method method) throws InterruptedException, ExecutionException { if (method != Method.METHOD_BEGIN) { realm.beginTransaction(); realm.createObject(Dog.class); } ExecutorService executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new Callable() { @Override public Boolean call() throws Exception { try { switch (method) { case METHOD_BEGIN: realm.beginTransaction(); break; case METHOD_COMMIT: realm.commitTransaction(); break; case METHOD_CANCEL: realm.cancelTransaction(); break; case METHOD_EXECUTE_TRANSACTION: realm.executeTransaction(realm -> fail()); break; case METHOD_EXECUTE_TRANSACTION_ASYNC: realm.executeTransactionAsync(realm -> fail()); break; case METHOD_DELETE_TYPE: realm.delete(AllTypes.class); break; case METHOD_DELETE_ALL: realm.deleteAll(); break; case METHOD_CREATE_OBJECT: realm.createObject(AllTypes.class); break; case METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY: realm.createObject(AllJavaTypes.class, 1L); break; case METHOD_COPY_TO_REALM: realm.copyToRealm(new AllTypes()); break; case METHOD_COPY_TO_REALM_OR_UPDATE: realm.copyToRealm(new AllTypesPrimaryKey()); break; case METHOD_CREATE_ALL_FROM_JSON: realm.createAllFromJson(AllTypes.class, ""[{}]""); break; case METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON: realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, ""[{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}]""); break; case METHOD_CREATE_FROM_JSON: realm.createObjectFromJson(AllTypes.class, ""{}""); break; case METHOD_CREATE_OR_UPDATE_FROM_JSON: realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, ""{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}""); break; case METHOD_INSERT_COLLECTION: realm.insert(Arrays.asList(new AllTypes(), new AllTypes())); break; case METHOD_INSERT_OBJECT: realm.insert(new AllTypes()); break; case METHOD_INSERT_OR_UPDATE_COLLECTION: realm.insert(Arrays.asList(new AllTypesPrimaryKey(), new AllTypesPrimaryKey())); break; case METHOD_INSERT_OR_UPDATE_OBJECT: realm.insertOrUpdate(new AllTypesPrimaryKey()); break; } return false; } catch (IllegalStateException ignored) { return true; } catch (RealmException jsonFailure) { // TODO: Eew. Reconsider how our JSON methods reports failure. See https://github.com/realm/realm-java/issues/1594 return (jsonFailure.getMessage().equals(""Could not map Json"")); } } }); boolean result = future.get(); if (method != Method.METHOD_BEGIN) { realm.cancelTransaction(); } return result; } @Test public void methodCalledOnWrongThread() throws ExecutionException, InterruptedException { for (Method method : Method.values()) { assertTrue(method.toString(), runMethodOnWrongThread(method)); } } // Calling methods on a wrong thread will fail. private boolean runMethodOnClosedRealm(final Method method) throws InterruptedException, ExecutionException { try { switch (method) { case METHOD_BEGIN: realm.beginTransaction(); break; case METHOD_COMMIT: realm.commitTransaction(); break; case METHOD_CANCEL: realm.cancelTransaction(); break; case METHOD_EXECUTE_TRANSACTION: realm.executeTransaction(realm -> fail()); break; case METHOD_EXECUTE_TRANSACTION_ASYNC: realm.executeTransactionAsync(realm -> fail()); break; case METHOD_DELETE_TYPE: realm.delete(AllTypes.class); break; case METHOD_DELETE_ALL: realm.deleteAll(); break; case METHOD_CREATE_OBJECT: realm.createObject(AllTypes.class); break; case METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY: realm.createObject(AllJavaTypes.class, 1L); break; case METHOD_COPY_TO_REALM: realm.copyToRealm(new AllTypes()); break; case METHOD_COPY_TO_REALM_OR_UPDATE: realm.copyToRealm(new AllTypesPrimaryKey()); break; case METHOD_CREATE_ALL_FROM_JSON: realm.createAllFromJson(AllTypes.class, ""[{}]""); break; case METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON: realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, ""[{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}]""); break; case METHOD_CREATE_FROM_JSON: realm.createObjectFromJson(AllTypes.class, ""{}""); break; case METHOD_CREATE_OR_UPDATE_FROM_JSON: realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, ""{\""columnLong\"":1,"" + "" \""columnBoolean\"": true}""); break; case METHOD_INSERT_COLLECTION: realm.insert(Arrays.asList(new AllTypes(), new AllTypes())); break; case METHOD_INSERT_OBJECT: realm.insert(new AllTypes()); break; case METHOD_INSERT_OR_UPDATE_COLLECTION: realm.insert(Arrays.asList(new AllTypesPrimaryKey(), new AllTypesPrimaryKey())); break; case METHOD_INSERT_OR_UPDATE_OBJECT: realm.insertOrUpdate(new AllTypesPrimaryKey()); break; } return false; } catch (IllegalStateException ignored) { return true; } catch (RealmException jsonFailure) { // TODO: Eew. Reconsider how our JSON methods reports failure. See https://github.com/realm/realm-java/issues/1594 return (jsonFailure.getMessage().equals(""Could not map Json"")); } } @Test public void methodCalledOnClosedRealm() throws ExecutionException, InterruptedException { realm.close(); for (Method method : Method.values()) { assertTrue(method.toString(), runMethodOnClosedRealm(method)); } } @Test public void commitTransaction() { populateTestRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnBoolean(true); realm.commitTransaction(); RealmResults resultList = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE + 1, resultList.size()); } @Test(expected = IllegalStateException.class) public void commitTransaction_afterCancelTransaction() { realm.beginTransaction(); realm.cancelTransaction(); realm.commitTransaction(); } @Test(expected = IllegalStateException.class) public void commitTransaction_twice() { realm.beginTransaction(); realm.commitTransaction(); realm.commitTransaction(); } @Test public void cancelTransaction() { populateTestRealm(); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.cancelTransaction(); assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).count()); try { realm.cancelTransaction(); fail(); } catch (IllegalStateException ignored) { } } @Test public void executeTransaction_null() { OsSharedRealm.VersionID oldVersion = realm.sharedRealm.getVersionID(); try { realm.executeTransaction(null); fail(""null transaction should throw""); } catch (IllegalArgumentException ignored) { } OsSharedRealm.VersionID newVersion = realm.sharedRealm.getVersionID(); assertEquals(oldVersion, newVersion); } @Test public void executeTransaction_success() { assertEquals(0, realm.where(Owner.class).count()); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName(""Owner""); } }); assertEquals(1, realm.where(Owner.class).count()); } @Test public void executeTransaction_canceled() { final AtomicReference thrownException = new AtomicReference(null); assertEquals(0, realm.where(Owner.class).count()); try { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName(""Owner""); thrownException.set(new RuntimeException(""Boom"")); throw thrownException.get(); } }); } catch (RuntimeException e) { //noinspection ThrowableResultOfMethodCallIgnored assertTrue(e == thrownException.get()); } assertEquals(0, realm.where(Owner.class).count()); } @Test public void executeTransaction_cancelInsideClosureThrowsException() { assertEquals(0, realm.where(Owner.class).count()); TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); try { RealmLog.add(testLogger); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName(""Owner""); realm.cancelTransaction(); throw new RuntimeException(""Boom""); } }); } catch (RuntimeException ignored) { // Ensures that we pass a valuable error message to the logger for developers. assertEquals(""Could not cancel transaction, not currently in a transaction."", testLogger.message); } finally { RealmLog.remove(testLogger); } assertEquals(0, realm.where(Owner.class).count()); } @Test @UiThreadTest public void executeTransaction_mainThreadWritesAllowed() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowWritesOnUiThread(true) .name(""ui_realm"") .build(); Realm uiRealm = Realm.getInstance(configuration); uiRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.insert(new Dog(""Snuffles"")); } }); RealmResults results = uiRealm.where(Dog.class).equalTo(""name"", ""Snuffles"").findAll(); assertEquals(1, results.size()); assertNotNull(results.first()); assertEquals(""Snuffles"", Objects.requireNonNull(results.first()).getName()); uiRealm.close(); } @Test @UiThreadTest public void executeTransaction_mainThreadWritesNotAllowed() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowWritesOnUiThread(false) .name(""ui_realm"") .build(); // Try-with-resources try (Realm uiRealm = Realm.getInstance(configuration)) { uiRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // no-op } }); fail(""the call to executeTransaction should have failed, this line should not be reached.""); } catch (RealmException e) { assertTrue(Objects.requireNonNull(e.getMessage()).contains(""allowWritesOnUiThread"")); } } @Test public void executeTransaction_runsOnNonUiThread() { RealmConfiguration configuration = configFactory.createConfigurationBuilder() .allowWritesOnUiThread(false) .name(""ui_realm"") .build(); Realm uiRealm = Realm.getInstance(configuration); uiRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // no-op } }); uiRealm.close(); } @Test public void delete_type() { // ** Deletes non existing table should succeed. realm.beginTransaction(); realm.delete(AllTypes.class); realm.commitTransaction(); // ** Deletes existing class, but leaves other classes classes. // Adds two classes. populateTestRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName(""Castro""); realm.commitTransaction(); // Clears. realm.beginTransaction(); realm.delete(Dog.class); realm.commitTransaction(); // Checks one class is cleared but other class is still there. RealmResults resultListTypes = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE, resultListTypes.size()); RealmResults resultListDogs = realm.where(Dog.class).findAll(); assertEquals(0, resultListDogs.size()); // ** delete() must throw outside a transaction. try { realm.delete(AllTypes.class); fail(""Expected exception""); } catch (IllegalStateException ignored) { } } private void createAndTestFilename(String language, String fileName) { RealmConfiguration realmConfig = configFactory.createConfiguration(fileName); Realm realm1 = Realm.getInstance(realmConfig); realm1.beginTransaction(); Dog dog1 = realm1.createObject(Dog.class); dog1.setName(""Rex""); realm1.commitTransaction(); realm1.close(); File file = new File(realmConfig.getPath()); assertTrue(language, file.exists()); Realm realm2 = Realm.getInstance(realmConfig); Dog dog2 = realm2.where(Dog.class).findFirst(); assertEquals(language, ""Rex"", dog2.getName()); realm2.close(); } // TODO Move to RealmConfigurationTests? @Test public void realmConfiguration_fileName() { createAndTestFilename(""American"", ""Washington""); createAndTestFilename(""Danish"", ""København""); createAndTestFilename(""Russian"", ""Москва""); createAndTestFilename(""Greek"", ""Αθήνα""); createAndTestFilename(""Chinese"", ""北京市""); createAndTestFilename(""Korean"", ""서울시""); createAndTestFilename(""Arabic"", ""الرياض""); createAndTestFilename(""India"", ""नई दिल्ली""); createAndTestFilename(""Japanese"", ""東京都""); } @Test public void utf8Tests() { realm.beginTransaction(); realm.delete(AllTypes.class); realm.commitTransaction(); String file = ""assets/unicode_codepoints.csv""; Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(file), ""UTF-8""); int i = 0; String currentUnicode = null; try { realm.beginTransaction(); while (scanner.hasNextLine()) { currentUnicode = scanner.nextLine(); char[] chars = Character.toChars(Integer.parseInt(currentUnicode, 16)); String codePoint = new String(chars); AllTypes o = realm.createObject(AllTypes.class); o.setColumnLong(i); o.setColumnString(codePoint); if (i > 1) { assertEquals(""Codepoint: "" + i + "" / "" + currentUnicode, codePoint, o.getColumnString()); // codepoint 0 is NULL, ignore for now. } i++; } realm.commitTransaction(); } catch (Exception e) { fail(""Failure, Codepoint: "" + i + "" / "" + currentUnicode + "" "" + e.getMessage()); } } private List getCharacterArray() { List chars_array = new ArrayList(); String file = ""assets/unicode_codepoints.csv""; Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(file), ""UTF-8""); int i = 0; String currentUnicode = null; try { while (scanner.hasNextLine()) { currentUnicode = scanner.nextLine(); char[] chars = Character.toChars(Integer.parseInt(currentUnicode, 16)); String codePoint = new String(chars); chars_array.add(codePoint); i++; } } catch (Exception e) { fail(""Failure, Codepoint: "" + i + "" / "" + currentUnicode + "" "" + e.getMessage()); } return chars_array; } // The test writes and reads random Strings. @Test public void unicodeStrings() { List charsArray = getCharacterArray(); // Change seed value for new random values. long seed = 20; Random random = new Random(seed); StringBuilder testChar = new StringBuilder(); realm.beginTransaction(); for (int i = 0; i < 1000; i++) { testChar.setLength(0); int length = random.nextInt(25); for (int j = 0; j < length; j++) { testChar.append(charsArray.get(random.nextInt(27261))); } StringOnly stringOnly = realm.createObject(StringOnly.class); // tests setter stringOnly.setChars(testChar.toString()); // tests getter realm.where(StringOnly.class).findFirst().getChars(); realm.delete(StringOnly.class); } realm.cancelTransaction(); } @Test public void getInstance_referenceCounting() { // At this point reference count should be one because of the setUp method. try { realm.where(AllTypes.class).count(); } catch (IllegalStateException e) { fail(); } // Makes sure the reference counter is per realm file. RealmConfiguration anotherConfig = configFactory.createConfiguration(""anotherRealm.realm""); Realm.deleteRealm(anotherConfig); Realm otherRealm = Realm.getInstance(anotherConfig); // Raises the reference. Realm realm = null; try { realm = Realm.getInstance(configFactory.createConfiguration()); } finally { if (realm != null) { realm.close(); } } try { // This should not fail because the reference is now 1. if (realm != null) { realm.where(AllTypes.class).count(); } } catch (IllegalStateException e) { fail(); } this.realm.close(); try { this.realm.where(AllTypes.class).count(); fail(); } catch (IllegalStateException ignored) { } try { otherRealm.where(AllTypes.class).count(); } catch (IllegalStateException e) { fail(); } finally { otherRealm.close(); } try { otherRealm.where(AllTypes.class).count(); fail(); } catch (IllegalStateException ignored) { } } @Test public void getInstance_referenceCounting_doubleClose() { realm.close(); realm.close(); // Counts down once too many. Counter is now potentially negative. realm = Realm.getInstance(configFactory.createConfiguration()); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); RealmResults queryResult = realm.where(AllTypes.class).findAll(); assertEquals(allTypes, queryResult.get(0)); realm.commitTransaction(); realm.close(); // This might not close the Realm if the reference count is wrong. // This should now fail due to the Realm being fully closed. thrown.expect(IllegalStateException.class); allTypes.getColumnString(); } @Test public void writeCopyTo() throws IOException { RealmConfiguration configA = configFactory.createConfiguration(""file1.realm""); RealmConfiguration configB = configFactory.createConfiguration(""file2.realm""); Realm.deleteRealm(configA); Realm.deleteRealm(configB); Realm realm1 = null; try { realm1 = Realm.getInstance(configA); realm1.beginTransaction(); AllTypes allTypes = realm1.createObject(AllTypes.class); allTypes.setColumnString(""Hello World""); realm1.commitTransaction(); realm1.writeCopyTo(new File(configB.getPath())); } finally { if (realm1 != null) { realm1.close(); } } // Copy is compacted i.e. smaller than original. File file1 = new File(configA.getPath()); File file2 = new File(configB.getPath()); assertTrue(file1.length() >= file2.length()); Realm realm2 = null; try { // Contents is copied too. realm2 = Realm.getInstance(configB); RealmResults results = realm2.where(AllTypes.class).findAll(); assertEquals(1, results.size()); assertEquals(""Hello World"", results.first().getColumnString()); } finally { if (realm2 != null) { realm2.close(); } } } @Test public void compactRealm() { final RealmConfiguration configuration = realm.getConfiguration(); realm.close(); realm = null; assertTrue(Realm.compactRealm(configuration)); realm = Realm.getInstance(configuration); } @Test public void compactRealm_failsIfOpen() { assertFalse(Realm.compactRealm(realm.getConfiguration())); } @Test public void compactRealm_encryptedEmptyRealm() { RealmConfiguration realmConfig = configFactory.createConfiguration(""enc.realm"", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); realm.close(); assertTrue(Realm.compactRealm(realmConfig)); realm = Realm.getInstance(realmConfig); assertFalse(realm.isClosed()); assertTrue(realm.isEmpty()); realm.close(); } @Test public void compactRealm_encryptedPopulatedRealm() { final int DATA_SIZE = 100; RealmConfiguration realmConfig = configFactory.createConfiguration(""enc.realm"", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); populateTestRealm(realm, DATA_SIZE); realm.close(); assertTrue(Realm.compactRealm(realmConfig)); realm = Realm.getInstance(realmConfig); assertFalse(realm.isClosed()); assertEquals(DATA_SIZE, realm.where(AllTypes.class).count()); realm.close(); } @Test public void compactRealm_emptyRealm() throws IOException { final String REALM_NAME = ""test.realm""; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); realm.close(); long before = new File(realmConfig.getPath()).length(); assertTrue(Realm.compactRealm(realmConfig)); long after = new File(realmConfig.getPath()).length(); assertTrue(before >= after); } @Test public void compactRealm_populatedRealm() throws IOException { final String REALM_NAME = ""test.realm""; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); populateTestRealm(realm, 100); realm.close(); long before = new File(realmConfig.getPath()).length(); assertTrue(Realm.compactRealm(realmConfig)); long after = new File(realmConfig.getPath()).length(); assertTrue(before >= after); } @Test public void compactRealm_onExternalStorage() { final File externalFilesDir = context.getExternalFilesDir(null); final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(externalFilesDir) .name(""external.realm"") .build(); Realm.deleteRealm(config); Realm realm = Realm.getInstance(config); realm.close(); assertTrue(Realm.compactRealm(config)); realm = Realm.getInstance(config); realm.close(); Realm.deleteRealm(config); } private void populateTestRealmForCompact(Realm realm, int sizeInMB) { byte[] oneMBData = new byte[1024 * 1024]; realm.beginTransaction(); for (int i = 0; i < sizeInMB; i++) { realm.createObject(AllTypes.class).setColumnBinary(oneMBData); } realm.commitTransaction(); } private Pair populateTestRealmAndCompactOnLaunch(CompactOnLaunchCallback compactOnLaunch) { return populateTestRealmAndCompactOnLaunch(compactOnLaunch, 1); } private Pair populateTestRealmAndCompactOnLaunch(CompactOnLaunchCallback compactOnLaunch, int sizeInMB) { final String REALM_NAME = ""test.realm""; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); populateTestRealmForCompact(realm, sizeInMB); realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); realm.close(); long before = new File(realmConfig.getPath()).length(); if (compactOnLaunch != null) { realmConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .compactOnLaunch(compactOnLaunch) .build(); } else { realmConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .compactOnLaunch() .build(); } realm = Realm.getInstance(realmConfig); realm.close(); long after = new File(realmConfig.getPath()).length(); return new Pair(before, after); } @Test public void compactOnLaunch_shouldCompact() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { assertTrue(totalBytes > usedBytes); return true; } }); assertTrue(results.first > results.second); } @Test public void compactOnLaunch_shouldNotCompact() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { assertTrue(totalBytes > usedBytes); return false; } }); assertEquals(results.first, results.second); } @Test public void compactOnLaunch_multipleThread() throws IOException { final String REALM_NAME = ""test.realm""; final AtomicInteger [MASK] = new AtomicInteger(0); final RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .name(REALM_NAME) .compactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { [MASK] .incrementAndGet(); return true; } }) .build(); Realm realm = Realm.getInstance(realmConfig); realm.close(); // WARNING: We need to init the schema first and close the Realm to make sure the relevant logic works in Object // Store. See https://github.com/realm/realm-object-store/blob/master/src/shared_realm.cpp#L58 // Called once. assertEquals(1, [MASK] .get()); realm = Realm.getInstance(realmConfig); assertEquals(2, [MASK] .get()); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm bgRealm = Realm.getInstance(realmConfig); bgRealm.close(); // compactOnLaunch should not be called anymore! assertEquals(2, [MASK] .get()); } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { fail(); } realm.close(); assertEquals(2, [MASK] .get()); } @Test public void compactOnLaunch_insufficientAmount() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { final long thresholdSize = 50 * 1024 * 1024; return (totalBytes > thresholdSize) && (((double) usedBytes / (double) totalBytes) < 0.5); } }, 1); final long thresholdSize = 50 * 1024 * 1024; assertTrue(results.first < thresholdSize); assertEquals(results.first, results.second); } @Test public void compactOnLaunch_throwsInTheCallback() { final RuntimeException exception = new RuntimeException(); final RealmConfiguration realmConfig = configFactory.createConfigurationBuilder() .name(""compactThrowsTest"") .compactOnLaunch(new CompactOnLaunchCallback() { @Override public boolean shouldCompact(long totalBytes, long usedBytes) { throw exception; } }) .build(); Realm realm = null; try { realm = Realm.getInstance(realmConfig); fail(); } catch (RuntimeException expected) { assertSame(exception, expected); } finally { if (realm != null) { realm.close(); } } } @Test public void defaultCompactOnLaunch() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(null, 50); final long thresholdSize = 50 * 1024 * 1024; assertTrue(results.first > thresholdSize); assertTrue(results.first > results.second); } @Test public void defaultCompactOnLaunch_onlyCallback() { DefaultCompactOnLaunchCallback callback = new DefaultCompactOnLaunchCallback(); final long thresholdSize = 50 * 1024 * 1024; final long big = thresholdSize + 1024; assertFalse(callback.shouldCompact(big, (long) (big * 0.6))); assertTrue(callback.shouldCompact(big, (long) (big * 0.3))); final long small = thresholdSize - 1024; assertFalse(callback.shouldCompact(small, (long) (small * 0.6))); assertFalse(callback.shouldCompact(small, (long) (small * 0.3))); } @Test public void defaultCompactOnLaunch_insufficientAmount() throws IOException { Pair results = populateTestRealmAndCompactOnLaunch(null, 1); final long thresholdSize = 50 * 1024 * 1024; assertTrue(results.first < thresholdSize); assertEquals(results.first, results.second); } @Test public void copyToRealm_null() { realm.beginTransaction(); try { realm.copyToRealm((AllTypes) null); fail(""Copying null objects into Realm should not be allowed""); } catch (IllegalArgumentException ignored) { } finally { realm.cancelTransaction(); } } @Test public void copyToRealm_managedObject() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnString(""Test""); realm.commitTransaction(); realm.beginTransaction(); AllTypes copiedAllTypes = realm.copyToRealm(allTypes); realm.commitTransaction(); assertTrue(allTypes == copiedAllTypes); } @Test public void copyToRealm_fromOtherRealm() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnString(""Test""); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(""12345""))); allTypes.setColumnObjectId(new ObjectId(TestHelper.randomObjectIdHexString())); allTypes.setColumnUUID(UUID.randomUUID()); allTypes.setColumnRealmAny(RealmAny.valueOf(UUID.randomUUID())); realm.commitTransaction(); RealmConfiguration realmConfig = configFactory.createConfiguration(""other-realm""); Realm otherRealm = Realm.getInstance(realmConfig); otherRealm.beginTransaction(); AllTypes copiedAllTypes = otherRealm.copyToRealm(allTypes); otherRealm.commitTransaction(); assertNotSame(allTypes, copiedAllTypes); // Same object in different Realms is not the same. assertEquals(allTypes.getColumnString(), copiedAllTypes.getColumnString()); // But data is still the same. otherRealm.close(); } @Test public void copyToRealm() { Date date = new Date(); Dog dog = new Dog(); dog.setName(""Fido""); RealmList list = new RealmList(); list.add(dog); AllTypes allTypes = new AllTypes(); allTypes.setColumnString(""String""); allTypes.setColumnLong(1L); allTypes.setColumnFloat(1F); allTypes.setColumnDouble(1D); allTypes.setColumnBoolean(true); allTypes.setColumnDate(date); allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDecimal128(new Decimal128(new BigDecimal(""12345""))); allTypes.setColumnObjectId(new ObjectId(TestHelper.generateObjectIdHexString(7))); allTypes.setColumnUUID(UUID.fromString(TestHelper.generateUUIDString(7))); allTypes.setColumnRealmAny(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(6)))); allTypes.setColumnRealmObject(dog); allTypes.setColumnRealmList(list); allTypes.setColumnStringList(new RealmList(""1"")); allTypes.setColumnBinaryList(new RealmList(new byte[] {1})); allTypes.setColumnBooleanList(new RealmList(true)); allTypes.setColumnLongList(new RealmList(1L)); allTypes.setColumnDoubleList(new RealmList(1D)); allTypes.setColumnFloatList(new RealmList(1F)); allTypes.setColumnDateList(new RealmList(new Date(1L))); allTypes.setColumnDecimal128List(new RealmList(new Decimal128(new BigDecimal(""54321"")))); allTypes.setColumnObjectIdList(new RealmList(new ObjectId(TestHelper.generateObjectIdHexString(5)))); allTypes.setColumnUUIDList(new RealmList<>(UUID.fromString(TestHelper.generateUUIDString(5)))); allTypes.setColumnRealmAnyList(new RealmList<>(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(7))))); realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(allTypes); realm.commitTransaction(); assertNotSame(allTypes, realmTypes); // Objects should not be considered equal. assertEquals(allTypes.getColumnString(), realmTypes.getColumnString()); // But they contain the same data. assertEquals(allTypes.getColumnLong(), realmTypes.getColumnLong()); assertEquals(allTypes.getColumnFloat(), realmTypes.getColumnFloat(), 0); assertEquals(allTypes.getColumnDouble(), realmTypes.getColumnDouble(), 0); assertEquals(allTypes.isColumnBoolean(), realmTypes.isColumnBoolean()); assertEquals(allTypes.getColumnDate(), realmTypes.getColumnDate()); assertArrayEquals(allTypes.getColumnBinary(), realmTypes.getColumnBinary()); assertEquals(allTypes.getColumnDecimal128(), realmTypes.getColumnDecimal128()); assertEquals(allTypes.getColumnObjectId(), realmTypes.getColumnObjectId()); assertEquals(allTypes.getColumnUUID(), realmTypes.getColumnUUID()); assertEquals(allTypes.getColumnRealmAny(), realmTypes.getColumnRealmAny()); assertEquals(allTypes.getColumnRealmObject().getName(), dog.getName()); assertEquals(list.size(), realmTypes.getColumnRealmList().size()); //noinspection ConstantConditions assertEquals(list.get(0).getName(), realmTypes.getColumnRealmList().get(0).getName()); assertEquals(1, realmTypes.getColumnStringList().size()); assertEquals(""1"", realmTypes.getColumnStringList().get(0)); assertEquals(1, realmTypes.getColumnBooleanList().size()); assertEquals(true, realmTypes.getColumnBooleanList().get(0)); assertEquals(1, realmTypes.getColumnBinaryList().size()); assertArrayEquals(new byte[] {1}, realmTypes.getColumnBinaryList().get(0)); assertEquals(1, realmTypes.getColumnLongList().size()); assertEquals((Long) 1L, realmTypes.getColumnLongList().get(0)); assertEquals(1, realmTypes.getColumnDoubleList().size()); assertEquals((Double) 1D, realmTypes.getColumnDoubleList().get(0)); assertEquals(1, realmTypes.getColumnFloatList().size()); assertEquals((Float) 1F, realmTypes.getColumnFloatList().get(0)); assertEquals(1, realmTypes.getColumnDateList().size()); assertEquals(new Date(1), realmTypes.getColumnDateList().get(0)); assertEquals(1, realmTypes.getColumnDecimal128List().size()); assertEquals(new Decimal128(new BigDecimal(""54321"")), realmTypes.getColumnDecimal128List().get(0)); assertEquals(1, realmTypes.getColumnObjectIdList().size()); assertEquals(new ObjectId(TestHelper.generateObjectIdHexString(5)), realmTypes.getColumnObjectIdList().get(0)); assertEquals(1, realmTypes.getColumnUUIDList().size()); assertEquals(UUID.fromString(TestHelper.generateUUIDString(5)), realmTypes.getColumnUUIDList().get(0)); assertEquals(1, realmTypes.getColumnRealmAnyList().size()); assertEquals(RealmAny.valueOf(UUID.fromString(TestHelper.generateUUIDString(7))), realmTypes.getColumnRealmAnyList().get(0)); } @Test public void copyToRealm_cyclicObjectReferences() { CyclicType oneCyclicType = new CyclicType(); oneCyclicType.setName(""One""); CyclicType anotherCyclicType = new CyclicType(); anotherCyclicType.setName(""Two""); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); CyclicType realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals(""One"", realmObject.getName()); assertEquals(""Two"", realmObject.getObject().getName()); assertEquals(2, realm.where(CyclicType.class).count()); // Tests copyToRealm overload that uses the Iterator. // Makes sure we reuse the same graph cache Map to avoid duplicates. realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(CyclicType.class).count()); realm.beginTransaction(); List cyclicTypes = realm.copyToRealm(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); assertEquals(2, cyclicTypes.size()); assertEquals(""One"", cyclicTypes.get(0).getName()); assertEquals(""Two"", cyclicTypes.get(1).getName()); assertEquals(2, realm.where(CyclicType.class).count()); } @Test public void copyToRealm_cyclicObjectReferencesWithPK() { CyclicTypePrimaryKey oneCyclicType = new CyclicTypePrimaryKey(1, ""One""); CyclicTypePrimaryKey anotherCyclicType = new CyclicTypePrimaryKey(2, ""Two""); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); CyclicTypePrimaryKey realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals(""One"", realmObject.getName()); assertEquals(""Two"", realmObject.getObject().getName()); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); // Tests copyToRealm overload that uses the Iterator. // Makes sure we reuse the same graph cache Map to avoid duplicates. realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(CyclicTypePrimaryKey.class).count()); realm.beginTransaction(); List cyclicTypes = realm.copyToRealm(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); assertEquals(2, cyclicTypes.size()); assertEquals(""One"", cyclicTypes.get(0).getName()); assertEquals(""Two"", cyclicTypes.get(1).getName()); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); } @Test public void copyToRealm_cyclicListReferences() { CyclicType oneCyclicType = new CyclicType(); oneCyclicType.setName(""One""); CyclicType anotherCyclicType = new CyclicType(); anotherCyclicType.setName(""Two""); oneCyclicType.setObjects(new RealmList<>(anotherCyclicType)); anotherCyclicType.setObjects(new RealmList<>(oneCyclicType)); realm.beginTransaction(); CyclicType realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals(""One"", realmObject.getName()); assertEquals(2, realm.where(CyclicType.class).count()); } // Checks that if a field has a null value, it gets converted to the default value for that type. @Test public void copyToRealm_convertsNullToDefaultValue() { realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(new AllTypes()); realm.commitTransaction(); assertEquals("""", realmTypes.getColumnString()); assertEquals(new Date(0), realmTypes.getColumnDate()); assertArrayEquals(new byte[0], realmTypes.getColumnBinary()); assertNotNull(realmTypes.getColumnRealmList()); assertNotNull(realmTypes.getColumnStringList()); assertNotNull(realmTypes.getColumnBinaryList()); assertNotNull(realmTypes.getColumnBooleanList()); assertNotNull(realmTypes.getColumnLongList()); assertNotNull(realmTypes.getColumnDoubleList()); assertNotNull(realmTypes.getColumnFloatList()); assertNotNull(realmTypes.getColumnDateList()); } // Check that using copyToRealm will set the primary key directly instead of first setting // it to the default value (which can fail). @Test public void copyToRealm_primaryKeyIsSetDirectly() { realm.beginTransaction(); realm.createObject(OwnerPrimaryKey.class, 0); realm.copyToRealm(new OwnerPrimaryKey(1, ""Foo"")); realm.commitTransaction(); assertEquals(2, realm.where(OwnerPrimaryKey.class).count()); } @Test public void copyToRealm_stringPrimaryKeyIsNull() { final long SECONDARY_FIELD_VALUE = 34992142L; TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, SECONDARY_FIELD_VALUE); RealmResults results = realm.where(PrimaryKeyAsString.class).findAll(); assertEquals(1, results.size()); assertEquals(null, results.first().getName()); assertEquals(SECONDARY_FIELD_VALUE, results.first().getId()); } @Test public void copyToRealm_boxedNumberPrimaryKeyIsNull() { final String SECONDARY_FIELD_VALUE = ""nullNumberPrimaryKeyObj""; final Class[] CLASSES = {PrimaryKeyAsBoxedByte.class, PrimaryKeyAsBoxedShort.class, PrimaryKeyAsBoxedInteger.class, PrimaryKeyAsBoxedLong.class}; TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, SECONDARY_FIELD_VALUE); TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); for (Class clazz : CLASSES) { RealmResults results = realm.where(clazz).findAll(); assertEquals(1, results.size()); assertEquals(null, ((NullPrimaryKey) results.first()).getId()); assertEquals(SECONDARY_FIELD_VALUE, ((NullPrimaryKey) results.first()).getName()); } } @Test public void copyToRealm_duplicatedPrimaryKeyThrows() { final String[] PRIMARY_KEY_TYPES = { ""String"", ""BoxedLong"", ""long"" }; for (String className : PRIMARY_KEY_TYPES) { String expectedKey = null; try { realm.beginTransaction(); switch (className) { case ""String"": { expectedKey = ""foo""; PrimaryKeyAsString obj = new PrimaryKeyAsString(""foo""); realm.copyToRealm(obj); realm.copyToRealm(obj); break; } case ""BoxedLong"": { expectedKey = Long.toString(Long.MIN_VALUE); PrimaryKeyAsBoxedLong obj = new PrimaryKeyAsBoxedLong(Long.MIN_VALUE, ""boxedlong""); realm.copyToRealm(obj); realm.copyToRealm(obj); break; } case ""long"": expectedKey = Long.toString(Long.MAX_VALUE); PrimaryKeyAsLong obj = new PrimaryKeyAsLong(Long.MAX_VALUE); realm.copyToRealm(obj); realm.copyToRealm(obj); break; default: } fail(""Null value as primary key already exists, but wasn't detected correctly""); } catch (RealmPrimaryKeyConstraintException expected) { assertTrue(""Exception message is: "" + expected.getMessage(), expected.getMessage().contains(""with an existing primary key value '""+ expectedKey +""'"")); } finally { realm.cancelTransaction(); } } } @Test public void copyToRealm_duplicatedNullPrimaryKeyThrows() { final String[] PRIMARY_KEY_TYPES = {""String"", ""BoxedByte"", ""BoxedShort"", ""BoxedInteger"", ""BoxedLong""}; TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, 0); TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, (String) null); TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, (String) null); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, (String) null); TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, (String) null); for (String className : PRIMARY_KEY_TYPES) { try { realm.beginTransaction(); switch (className) { case ""String"": realm.copyToRealm(new PrimaryKeyAsString()); break; case ""BoxedByte"": realm.copyToRealm(new PrimaryKeyAsBoxedByte()); break; case ""BoxedShort"": realm.copyToRealm(new PrimaryKeyAsBoxedShort()); break; case ""BoxedInteger"": realm.copyToRealm(new PrimaryKeyAsBoxedInteger()); break; case ""BoxedLong"": realm.copyToRealm(new PrimaryKeyAsBoxedLong()); break; default: } fail(""Null value as primary key already exists, but wasn't detected correctly""); } catch (RealmPrimaryKeyConstraintException expected) { assertTrue(""Exception message is: "" + expected.getMessage(), expected.getMessage().contains(""with an existing primary key value 'null'"")); } finally { realm.cancelTransaction(); } } } @Test public void copyToRealm_doNotCopyReferencedObjectIfManaged() { realm.beginTransaction(); // Child object is managed by Realm. CyclicTypePrimaryKey childObj = realm.createObject(CyclicTypePrimaryKey.class, 1); childObj.setName(""Child""); // Parent object is an unmanaged object. CyclicTypePrimaryKey parentObj = new CyclicTypePrimaryKey(2); parentObj.setObject(childObj); realm.copyToRealm(parentObj); realm.commitTransaction(); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); } @Test public void copyToRealm_list() { Dog dog1 = new Dog(); dog1.setName(""Dog 1""); Dog dog2 = new Dog(); dog2.setName(""Dog 2""); RealmList list = new RealmList(); list.addAll(Arrays.asList(dog1, dog2)); realm.beginTransaction(); List copiedList = new ArrayList(realm.copyToRealm(list)); realm.commitTransaction(); assertEquals(2, copiedList.size()); assertEquals(dog1.getName(), copiedList.get(0).getName()); assertEquals(dog2.getName(), copiedList.get(1).getName()); } @Test public void copyToRealm_objectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); realm.beginTransaction(); final Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(dog); fail(); } catch (IllegalArgumentException expected) { assertEquals(""Objects which belong to Realm instances in other threads cannot be copied into this"" + "" Realm instance."", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } @Test public void copyToRealmOrUpdate_null() { realm.beginTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyToRealmOrUpdate((AllTypes) null); } @Test public void copyToRealmOrUpdate_stringPrimaryKeyFieldIsNull() { final long SECONDARY_FIELD_VALUE = 2192841L; final long SECONDARY_FIELD_UPDATED = 44887612L; PrimaryKeyAsString nullPrimaryKeyObj = TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsString.class).findAll(); assertEquals(1, result.size()); assertEquals(null, result.first().getName()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setId(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsString.class).findFirst().getId()); } @Test public void copyToRealmOrUpdate_boxedBytePrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullBytePrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullBytePrimaryKeyObjUpdated""; PrimaryKeyAsBoxedByte nullPrimaryKeyObj = TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedByte.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedByte.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedShortPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullShortPrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullShortPrimaryKeyObjUpdated""; PrimaryKeyAsBoxedShort nullPrimaryKeyObj = TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedShort.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedShort.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedIntegerPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullIntegerPrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullIntegerPrimaryKeyObjUpdated""; PrimaryKeyAsBoxedInteger nullPrimaryKeyObj = TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedInteger.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedInteger.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedLongPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = ""nullLongPrimaryKeyObj""; final String SECONDARY_FIELD_UPDATED = ""nullLongPrimaryKeyObjUpdated""; PrimaryKeyAsBoxedLong nullPrimaryKeyObj = TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); RealmResults result = realm.where(PrimaryKeyAsBoxedLong.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedLong.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_noPrimaryKeyField() { realm.beginTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyToRealmOrUpdate(new AllTypes()); } @Test public void copyToRealmOrUpdate_addNewObjects() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); realm.copyToRealm(obj); PrimaryKeyAsLong obj2 = new PrimaryKeyAsLong(); obj2.setId(2); obj2.setName(""Bar""); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(2, realm.where(PrimaryKeyAsLong.class).count()); } @Test public void copyToRealmOrUpdate_updateExistingObject() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnString(""Foo""); obj.setColumnLong(1); obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, ""Dog1"")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, ""Dog2""))); obj.setColumnBoxedBoolean(true); obj.setColumnStringList(new RealmList<>(""1"")); obj.setColumnBooleanList(new RealmList<>(false)); obj.setColumnBinaryList(new RealmList<>(new byte[] {1})); obj.setColumnLongList(new RealmList<>(1L)); obj.setColumnDoubleList(new RealmList<>(1D)); obj.setColumnFloatList(new RealmList<>(1F)); obj.setColumnDateList(new RealmList<>(new Date(1L))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnString(""Bar""); obj2.setColumnLong(1); obj2.setColumnFloat(2.23F); obj2.setColumnDouble(2.234D); obj2.setColumnBoolean(true); obj2.setColumnBinary(new byte[] {2, 3, 4}); obj2.setColumnDate(new Date(2000)); obj2.setColumnRealmObject(new DogPrimaryKey(3, ""Dog3"")); obj2.setColumnRealmList(new RealmList(new DogPrimaryKey(4, ""Dog4""))); obj2.setColumnBoxedBoolean(false); obj2.setColumnStringList(new RealmList<>(""2"", ""3"")); obj2.setColumnBooleanList(new RealmList<>(true, false)); obj2.setColumnBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); obj2.setColumnLongList(new RealmList<>(2L, 3L)); obj2.setColumnDoubleList(new RealmList<>(2D, 3D)); obj2.setColumnFloatList(new RealmList<>(2F, 3F)); obj2.setColumnDateList(new RealmList<>(new Date(2L), new Date(3L))); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst(); // Checks that the the only element has all its properties updated. assertEquals(""Bar"", obj.getColumnString()); assertEquals(1, obj.getColumnLong()); assertEquals(2.23F, obj.getColumnFloat(), 0); assertEquals(2.234D, obj.getColumnDouble(), 0); assertEquals(true, obj.isColumnBoolean()); assertArrayEquals(new byte[] {2, 3, 4}, obj.getColumnBinary()); assertEquals(new Date(2000), obj.getColumnDate()); assertEquals(""Dog3"", obj.getColumnRealmObject().getName()); assertEquals(1, obj.getColumnRealmList().size()); assertEquals(""Dog4"", obj.getColumnRealmList().get(0).getName()); assertFalse(obj.getColumnBoxedBoolean()); assertEquals(2, obj.getColumnStringList().size()); assertEquals(""2"", obj.getColumnStringList().get(0)); assertEquals(""3"", obj.getColumnStringList().get(1)); assertEquals(2, obj.getColumnBooleanList().size()); assertEquals(true, obj.getColumnBooleanList().get(0)); assertEquals(false, obj.getColumnBooleanList().get(1)); assertEquals(2, obj.getColumnBinaryList().size()); assertArrayEquals(new byte[] {2}, obj.getColumnBinaryList().get(0)); assertArrayEquals(new byte[] {3}, obj.getColumnBinaryList().get(1)); assertEquals(2, obj.getColumnLongList().size()); assertEquals((Long) 2L, obj.getColumnLongList().get(0)); assertEquals((Long) 3L, obj.getColumnLongList().get(1)); assertEquals(2, obj.getColumnDoubleList().size()); assertEquals((Double) 2D, obj.getColumnDoubleList().get(0)); assertEquals((Double) 3D, obj.getColumnDoubleList().get(1)); assertEquals(2, obj.getColumnFloatList().size()); assertEquals((Float) 2F, obj.getColumnFloatList().get(0)); assertEquals((Float) 3F, obj.getColumnFloatList().get(1)); assertEquals(2, obj.getColumnDateList().size()); assertEquals(new Date(2L), obj.getColumnDateList().get(0)); assertEquals(new Date(3L), obj.getColumnDateList().get(1)); } @Test public void copyToRealmOrUpdate_overrideOwnList() { realm.beginTransaction(); AllJavaTypes managedObj = realm.createObject(AllJavaTypes.class, 1); managedObj.getFieldList().add(managedObj); AllJavaTypes unmanagedObj = realm.copyFromRealm(managedObj); unmanagedObj.setFieldList(managedObj.getFieldList()); managedObj = realm.copyToRealmOrUpdate(unmanagedObj); assertEquals(1, managedObj.getFieldList().size()); assertEquals(1, managedObj.getFieldList().first().getFieldId()); } @Test public void copyToRealmOrUpdate_cyclicObject() { CyclicTypePrimaryKey oneCyclicType = new CyclicTypePrimaryKey(1); oneCyclicType.setName(""One""); CyclicTypePrimaryKey anotherCyclicType = new CyclicTypePrimaryKey(2); anotherCyclicType.setName(""Two""); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); realm.copyToRealm(oneCyclicType); realm.commitTransaction(); oneCyclicType.setName(""Three""); anotherCyclicType.setName(""Four""); realm.beginTransaction(); realm.copyToRealmOrUpdate(oneCyclicType); realm.commitTransaction(); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); assertEquals(""Three"", realm.where(CyclicTypePrimaryKey.class).equalTo(""id"", 1).findFirst().getName()); } // Checks that an unmanaged object with only default values can override data. @Test public void copyToRealmOrUpdate_defaultValuesOverrideExistingData() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnString(""Foo""); obj.setColumnLong(1); obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, ""Dog1"")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, ""Dog2""))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnLong(1); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst(); assertNull(obj.getColumnString()); assertEquals(1, obj.getColumnLong()); assertEquals(0.0F, obj.getColumnFloat(), 0); assertEquals(0.0D, obj.getColumnDouble(), 0); assertEquals(false, obj.isColumnBoolean()); assertNull(obj.getColumnBinary()); assertNull(obj.getColumnDate()); assertNull(obj.getColumnRealmObject()); assertEquals(0, obj.getColumnRealmList().size()); } // Tests that if references to objects are removed, the objects are still in the Realm. @Test public void copyToRealmOrUpdate_referencesNotDeleted() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnLong(1); obj.setColumnRealmObject(new DogPrimaryKey(1, ""Dog1"")); obj.setColumnRealmList(new RealmList(new DogPrimaryKey(2, ""Dog2""))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnLong(1); obj2.setColumnRealmObject(new DogPrimaryKey(3, ""Dog3"")); obj2.setColumnRealmList(new RealmList(new DogPrimaryKey(4, ""Dog4""))); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); assertEquals(4, realm.where(DogPrimaryKey.class).count()); } @Test public void copyToRealmOrUpdate_primaryKeyMixInObjectGraph() { // Crate Object graph where tier 2 consists of 1 object with primary key and one doesn't. // Tier 3 both have objects with primary keys. // // PK // / \ // PK nonPK // | | // PK PK DogPrimaryKey dog = new DogPrimaryKey(1, ""Dog""); OwnerPrimaryKey owner = new OwnerPrimaryKey(1, ""Owner""); owner.setDog(dog); Cat cat = new Cat(); cat.setScaredOfDog(dog); PrimaryKeyMix mixObject = new PrimaryKeyMix(1); mixObject.setDogOwner(owner); mixObject.setCat(cat); realm.beginTransaction(); PrimaryKeyMix realmObject = realm.copyToRealmOrUpdate(mixObject); realm.commitTransaction(); assertEquals(""Dog"", realmObject.getCat().getScaredOfDog().getName()); assertEquals(""Dog"", realmObject.getDogOwner().getDog().getName()); } @Test public void copyToRealmOrUpdate_iterable() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); realm.copyToRealm(obj); PrimaryKeyAsLong obj2 = new PrimaryKeyAsLong(); obj2.setId(1); obj2.setName(""Bar""); PrimaryKeyAsLong obj3 = new PrimaryKeyAsLong(); obj3.setId(1); obj3.setName(""Baz""); realm.copyToRealmOrUpdate(Arrays.asList(obj2, obj3)); } }); assertEquals(1, realm.where(PrimaryKeyAsLong.class).count()); assertEquals(""Baz"", realm.where(PrimaryKeyAsLong.class).findFirst().getName()); } // Tests that a collection of objects with references all gets copied. @Test public void copyToRealmOrUpdate_iterableChildObjects() { DogPrimaryKey dog = new DogPrimaryKey(1, ""Snoop""); AllTypesPrimaryKey allTypes1 = new AllTypesPrimaryKey(); allTypes1.setColumnLong(1); allTypes1.setColumnRealmObject(dog); AllTypesPrimaryKey allTypes2 = new AllTypesPrimaryKey(); allTypes1.setColumnLong(2); allTypes2.setColumnRealmObject(dog); realm.beginTransaction(); realm.copyToRealmOrUpdate(Arrays.asList(allTypes1, allTypes2)); realm.commitTransaction(); assertEquals(2, realm.where(AllTypesPrimaryKey.class).count()); assertEquals(1, realm.where(DogPrimaryKey.class).count()); } @Test public void copyToRealmOrUpdate_objectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); realm.beginTransaction(); final OwnerPrimaryKey ownerPrimaryKey = realm.createObject(OwnerPrimaryKey.class, 0); realm.commitTransaction(); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(ownerPrimaryKey); fail(); } catch (IllegalArgumentException expected) { assertEquals(""Objects which belong to Realm instances in other threads cannot be copied into this"" + "" Realm instance."", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } @Test public void copyToRealmOrUpdate_listHasObjectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); final OwnerPrimaryKey ownerPrimaryKey = new OwnerPrimaryKey(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); ownerPrimaryKey.setDogs(new RealmList(dog)); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(ownerPrimaryKey); fail(); } catch (IllegalArgumentException expected) { assertEquals(""Objects which belong to Realm instances in other threads cannot be copied into this"" + "" Realm instance."", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } // Test to reproduce issue https://github.com/realm/realm-java/issues/4957 @Test public void copyToRealmOrUpdate_bug4957() { Object4957 listElement = new Object4957(); listElement.setId(1); Object4957 parent = new Object4957(); parent.setId(0); parent.getChildList().add(listElement); // parentCopy has same fields as the parent does. But they are not the same object. Object4957 parentCopy = new Object4957(); parentCopy.setId(0); parentCopy.getChildList().add(listElement); parent.setChild(parentCopy); parentCopy.setChild(parentCopy); realm.beginTransaction(); Object4957 managedParent = realm.copyToRealmOrUpdate(parent); realm.commitTransaction(); // The original bug fails here. It resulted the listElement has been added to the list twice. // Because of the parent and parentCopy are not the same object, proxy will miss the cache to know the object // has been created before. But it does know they share the same PK value. assertEquals(1, managedParent.getChildList().size()); // insertOrUpdate doesn't have the problem! realm.beginTransaction(); realm.deleteAll(); realm.insertOrUpdate(parent); realm.commitTransaction(); managedParent = realm.where(Object4957.class).findFirst(); assertEquals(1, managedParent.getChildList().size()); } @Test public void getInstance_differentEncryptionKeys() { byte[] key1 = TestHelper.getRandomKey(42); byte[] key2 = TestHelper.getRandomKey(42); // Makes sure the key is the same, but in two different instances. assertArrayEquals(key1, key2); assertTrue(key1 != key2); final String ENCRYPTED_REALM = ""differentKeys.realm""; Realm realm1 = null; Realm realm2 = null; try { realm1 = Realm.getInstance(configFactory.createConfiguration(ENCRYPTED_REALM, key1)); try { realm2 = Realm.getInstance(configFactory.createConfiguration(ENCRYPTED_REALM, key2)); } catch (Exception e) { fail(""Unexpected exception: "" + e); } finally { if (realm2 != null) { realm2.close(); } } } finally { if (realm1 != null) { realm1.close(); } } } @Test public void writeEncryptedCopyTo() throws Exception { populateTestRealm(); long before = realm.where(AllTypes.class).count(); assertEquals(TEST_DATA_SIZE, before); // Configures test realms. final String ENCRYPTED_REALM_FILE_NAME = ""encryptedTestRealm.realm""; final String RE_ENCRYPTED_REALM_FILE_NAME = ""reEncryptedTestRealm.realm""; final String DECRYPTED_REALM_FILE_NAME = ""decryptedTestRealm.realm""; RealmConfiguration encryptedRealmConfig = configFactory.createConfiguration(ENCRYPTED_REALM_FILE_NAME, TestHelper.getRandomKey()); RealmConfiguration reEncryptedRealmConfig = configFactory.createConfiguration(RE_ENCRYPTED_REALM_FILE_NAME, TestHelper.getRandomKey()); RealmConfiguration decryptedRealmConfig = configFactory.createConfiguration(DECRYPTED_REALM_FILE_NAME); // Writes encrypted copy from a unencrypted Realm. File destination = new File(encryptedRealmConfig.getPath()); realm.writeEncryptedCopyTo(destination, encryptedRealmConfig.getEncryptionKey()); Realm encryptedRealm = null; try { // Verifies encrypted Realm and writes new encrypted copy with a new key. encryptedRealm = Realm.getInstance(encryptedRealmConfig); assertEquals(TEST_DATA_SIZE, encryptedRealm.where(AllTypes.class).count()); destination = new File(reEncryptedRealmConfig.getPath()); encryptedRealm.writeEncryptedCopyTo(destination, reEncryptedRealmConfig.getEncryptionKey()); // Verifies re-encrypted copy. Realm reEncryptedRealm = null; try { reEncryptedRealm = Realm.getInstance(reEncryptedRealmConfig); assertEquals(TEST_DATA_SIZE, reEncryptedRealm.where(AllTypes.class).count()); } finally { if (reEncryptedRealm != null) { reEncryptedRealm.close(); if (!Realm.deleteRealm(reEncryptedRealmConfig)) { fail(); } } } // Writes non-encrypted copy from the encrypted version. destination = new File(decryptedRealmConfig.getPath()); encryptedRealm.writeEncryptedCopyTo(destination, null); // Verifies decrypted Realm and cleans up. Realm decryptedRealm = null; try { decryptedRealm = Realm.getInstance(decryptedRealmConfig); assertEquals(TEST_DATA_SIZE, decryptedRealm.where(AllTypes.class).count()); } finally { if (decryptedRealm != null) { decryptedRealm.close(); if (!Realm.deleteRealm(decryptedRealmConfig)) { fail(); } } } } finally { if (encryptedRealm != null) { encryptedRealm.close(); if (!Realm.deleteRealm(encryptedRealmConfig)) { fail(); } } } } @Test public void writeEncryptedCopyTo_wrongKeyLength() { byte[] wrongLengthKey = new byte[42]; File destination = new File(configFactory.getRoot(), ""wrong_key.realm""); thrown.expect(IllegalArgumentException.class); realm.writeEncryptedCopyTo(destination, wrongLengthKey); } @Test public void deleteRealm_failures() { final String OTHER_REALM_NAME = ""yetAnotherRealm.realm""; RealmConfiguration configA = configFactory.createConfiguration(); RealmConfiguration configB = configFactory.createConfiguration(OTHER_REALM_NAME); // This instance is already cached because of the setUp() method so this deletion should throw. try { Realm.deleteRealm(configA); fail(); } catch (IllegalStateException ignored) { } // Creates a new Realm file. Realm yetAnotherRealm = Realm.getInstance(configB); // Deleting it should fail. try { Realm.deleteRealm(configB); fail(); } catch (IllegalStateException ignored) { } // But now that we close it deletion should work. yetAnotherRealm.close(); try { Realm.deleteRealm(configB); } catch (Exception e) { fail(); } } // TODO Does this test something meaningfull not tested elsewhere? @Test public void setter_updateField() throws Exception { realm.beginTransaction(); // Creates an owner with two dogs. OwnerPrimaryKey owner = realm.createObject(OwnerPrimaryKey.class, 1); owner.setName(""Jack""); Dog rex = realm.createObject(Dog.class); rex.setName(""Rex""); Dog fido = realm.createObject(Dog.class); fido.setName(""Fido""); owner.getDogs().add(rex); owner.getDogs().add(fido); assertEquals(2, owner.getDogs().size()); // Changing the name of the owner should not affect the number of dogs. owner.setName(""Peter""); assertEquals(2, owner.getDogs().size()); // Updating the user should not affect it either. This is actually a no-op since owner is a Realm backed object. OwnerPrimaryKey owner2 = realm.copyToRealmOrUpdate(owner); assertEquals(2, owner.getDogs().size()); assertEquals(2, owner2.getDogs().size()); realm.commitTransaction(); } @Test public void deleteRealm() throws InterruptedException { File tempDir = new File(configFactory.getRoot(), ""delete_test_dir""); File tempDirRenamed = new File(configFactory.getRoot(), ""delete_test_dir_2""); assertTrue(tempDir.mkdir()); final RealmConfiguration configuration = configFactory.createConfigurationBuilder() .directory(tempDir) .build(); final CountDownLatch bgThreadReadyLatch = new CountDownLatch(1); final CountDownLatch readyToCloseLatch = new CountDownLatch(1); final CountDownLatch closedLatch = new CountDownLatch(1); Realm realm = Realm.getInstance(configuration); // Creates another Realm to ensure the log files are generated. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(configuration); bgThreadReadyLatch.countDown(); TestHelper.awaitOrFail(readyToCloseLatch); realm.close(); closedLatch.countDown(); } }).start(); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); // Waits for bg thread's opening the same Realm. TestHelper.awaitOrFail(bgThreadReadyLatch); // A core upgrade might change the location of the files assertTrue(tempDir.renameTo(tempDirRenamed)); readyToCloseLatch.countDown(); realm.close(); TestHelper.awaitOrFail(closedLatch); // Now we get log files back! assertTrue(tempDirRenamed.renameTo(tempDir)); assertTrue(Realm.deleteRealm(configuration)); assertEquals(1, tempDir.listFiles().length); // Lock file should never be deleted File lockFile = new File(configuration.getPath() + "".lock""); assertTrue(lockFile.exists()); } // Tests that all methods that require a transaction. (ie. any function that mutates Realm data) @Test public void callMutableMethodOutsideTransaction() throws JSONException, IOException { // Prepares unmanaged object data. AllTypesPrimaryKey t = new AllTypesPrimaryKey(); List ts = Arrays.asList(t, t); // Prepares JSON data. String jsonObjStr = ""{ \""columnLong\"" : 1 }""; JSONObject jsonObj = new JSONObject(jsonObjStr); InputStream jsonObjStream = TestHelper.stringToStream(jsonObjStr); InputStream jsonObjStream2 = TestHelper.stringToStream(jsonObjStr); String jsonArrStr = "" [{ \""columnLong\"" : 1 }] ""; JSONArray jsonArr = new JSONArray(jsonArrStr); InputStream jsonArrStream = TestHelper.stringToStream(jsonArrStr); InputStream jsonArrStream2 = TestHelper.stringToStream(jsonArrStr); // Tests all methods that should require a transaction. try { realm.createObject(AllTypes.class); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealm(t); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealm(ts); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealmOrUpdate(t); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealmOrUpdate(ts); fail(); } catch (IllegalStateException expected) {} try { realm.delete(AllTypes.class); fail(); } catch (IllegalStateException expected) {} try { realm.deleteAll(); fail(); } catch (IllegalStateException expected) {} try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, jsonObjStream); fail(); } catch (IllegalStateException expected) {} } try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2); fail(); } catch (IllegalStateException expected) {} } try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream); fail(); } catch (IllegalStateException expected) {} } try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2); fail(); } catch (IllegalStateException expected) {} } } @Test public void createObject_cannotCreateDynamicRealmObject() { realm.beginTransaction(); try { realm.createObject(DynamicRealmObject.class); fail(); } catch (RealmException ignored) { } } @Test(expected = RealmException.class) public void createObject_absentPrimaryKeyThrows() { realm.beginTransaction(); realm.createObject(DogPrimaryKey.class); } @Test public void createObjectWithPrimaryKey() { realm.beginTransaction(); AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); assertEquals(1, realm.where(AllJavaTypes.class).count()); assertEquals(42, obj.getFieldId()); } @Test public void createObjectWithPrimaryKey_noPrimaryKeyField() { realm.beginTransaction(); try { realm.createObject(AllTypes.class, 42); fail(); } catch (IllegalStateException ignored) { } } @Test public void createObjectWithPrimaryKey_wrongValueType() { realm.beginTransaction(); try { realm.createObject(AllJavaTypes.class, ""fortyTwo""); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void createObjectWithPrimaryKey_valueAlreadyExists() { realm.beginTransaction(); realm.createObject(AllJavaTypes.class, 42); try { realm.createObject(AllJavaTypes.class, 42); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } } @Test public void createObjectWithPrimaryKey_null() { // Byte realm.beginTransaction(); PrimaryKeyAsBoxedByte primaryKeyAsBoxedByte = realm.createObject(PrimaryKeyAsBoxedByte.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedByte.class).count()); assertNull(primaryKeyAsBoxedByte.getId()); // Short realm.beginTransaction(); PrimaryKeyAsBoxedShort primaryKeyAsBoxedShort = realm.createObject(PrimaryKeyAsBoxedShort.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedShort.class).count()); assertNull(primaryKeyAsBoxedShort.getId()); // Integer realm.beginTransaction(); PrimaryKeyAsBoxedInteger primaryKeyAsBoxedInteger = realm.createObject(PrimaryKeyAsBoxedInteger.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedInteger.class).count()); assertNull(primaryKeyAsBoxedInteger.getId()); // Long realm.beginTransaction(); PrimaryKeyAsBoxedLong primaryKeyAsBoxedLong = realm.createObject(PrimaryKeyAsBoxedLong.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedLong.class).count()); assertNull(primaryKeyAsBoxedLong.getId()); // String realm.beginTransaction(); PrimaryKeyAsString primaryKeyAsString = realm.createObject(PrimaryKeyAsString.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsString.class).count()); assertNull(primaryKeyAsString.getName()); } @Test public void createObjectWithPrimaryKey_nullOnRequired() { realm.beginTransaction(); // Byte try { realm.createObject(PrimaryKeyRequiredAsBoxedByte.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Short try { realm.createObject(PrimaryKeyRequiredAsBoxedShort.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Integer try { realm.createObject(PrimaryKeyRequiredAsBoxedInteger.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Long try { realm.createObject(PrimaryKeyRequiredAsBoxedLong.class, null); fail(); } catch (IllegalArgumentException ignored) { } // String try { realm.createObject(PrimaryKeyRequiredAsString.class, null); fail(); } catch (IllegalArgumentException ignored) { } realm.cancelTransaction(); } @Test public void createObjectWithPrimaryKey_nullDuplicated() { realm.beginTransaction(); // Byte realm.createObject(PrimaryKeyAsBoxedByte.class, null); try { realm.createObject(PrimaryKeyAsBoxedByte.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Short realm.createObject(PrimaryKeyAsBoxedShort.class, null); try { realm.createObject(PrimaryKeyAsBoxedShort.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Integer realm.createObject(PrimaryKeyAsBoxedInteger.class, null); try { realm.createObject(PrimaryKeyAsBoxedInteger.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Long realm.createObject(PrimaryKeyAsBoxedLong.class, null); try { realm.createObject(PrimaryKeyAsBoxedLong.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // String realm.createObject(PrimaryKeyAsString.class, null); try { realm.createObject(PrimaryKeyAsString.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } realm.cancelTransaction(); } @Test public void createObject_defaultValueFromModelField() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueOfField with non-default primary key value. realm.createObject(DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueOfField.lastRandomStringValue; testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_STRING, DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_SHORT, DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_INT, DefaultValueOfField.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG, DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BYTE, DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_FLOAT, DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_DOUBLE, DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BOOLEAN, DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_DATE, DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BINARY, DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_OBJECT + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } @Test public void createObject_overwriteNullifiedLinkWithDefaultValue() { final DefaultValueOverwriteNullLink created; realm.beginTransaction(); created = realm.createObject(DefaultValueOverwriteNullLink.class); realm.commitTransaction(); assertEquals(created.getExpectedKeyOfFieldObject(), created.getFieldObject().getFieldRandomPrimaryKey()); } @Test public void createObject_defaultValueFromModelConstructor() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueConstructor with non-default primary key value. realm.createObject(DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueConstructor.lastRandomStringValue; testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_STRING, DefaultValueConstructor.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_SHORT, DefaultValueConstructor.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_INT, DefaultValueConstructor.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG, DefaultValueConstructor.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BYTE, DefaultValueConstructor.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_FLOAT, DefaultValueConstructor.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_DOUBLE, DefaultValueConstructor.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BOOLEAN, DefaultValueConstructor.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_DATE, DefaultValueConstructor.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BINARY, DefaultValueConstructor.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_OBJECT + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } @Test public void createObject_defaultValueSetterInConstructor() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueSetter with non-default primary key value. realm.createObject(DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueSetter.lastRandomStringValue; testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_STRING, DefaultValueSetter.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_SHORT, DefaultValueSetter.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_INT, DefaultValueSetter.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG, DefaultValueSetter.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BYTE, DefaultValueSetter.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_FLOAT, DefaultValueSetter.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_DOUBLE, DefaultValueSetter.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BOOLEAN, DefaultValueSetter.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_DATE, DefaultValueSetter.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BINARY, DefaultValueSetter.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_OBJECT + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LIST + ""."" + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); } @Test public void createObject_defaultValueFromOtherConstructor() { realm.beginTransaction(); DefaultValueFromOtherConstructor obj = realm.createObject(DefaultValueFromOtherConstructor.class); realm.commitTransaction(); assertEquals(42, obj.getFieldLong()); } @Test public void copyToRealm_defaultValuesAreIgnored() { final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + "".modified""; final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + "".modified""; final String fieldRandomStringValue = ""non-random""; final short fieldShortValue = (short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1); final int fieldIntValue = DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1; final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; final long fieldLongValue = DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1; final byte fieldByteValue = (byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1); final float fieldFloatValue = DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1; final double fieldDoubleValue = DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1; final boolean fieldBooleanValue = !DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE; final Date fieldDateValue = new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1); final byte[] fieldBinaryValue = {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}; final int fieldObjectIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1; final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; final DefaultValueOfField managedObj; realm.beginTransaction(); { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(fieldIgnoredValue); obj.setFieldString(fieldStringValue); obj.setFieldRandomString(fieldRandomStringValue); obj.setFieldShort(fieldShortValue); obj.setFieldInt(fieldIntValue); obj.setFieldLongPrimaryKey(fieldLongPrimaryKeyValue); obj.setFieldLong(fieldLongValue); obj.setFieldByte(fieldByteValue); obj.setFieldFloat(fieldFloatValue); obj.setFieldDouble(fieldDoubleValue); obj.setFieldBoolean(fieldBooleanValue); obj.setFieldDate(fieldDateValue); obj.setFieldBinary(fieldBinaryValue); final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); fieldObjectValue.setFieldInt(fieldObjectIntValue); obj.setFieldObject(fieldObjectValue); final RealmList list = new RealmList(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(fieldListIntValue); list.add(listItem); obj.setFieldList(list); obj.setFieldStringList(new RealmList<>(""2"", ""3"")); obj.setFieldBooleanList(new RealmList<>(true, false)); obj.setFieldBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); obj.setFieldLongList(new RealmList<>(2L, 3L)); obj.setFieldIntegerList(new RealmList<>(2, 3)); obj.setFieldShortList(new RealmList<>((short) 2, (short) 3)); obj.setFieldByteList(new RealmList<>((byte) 2, (byte) 3)); obj.setFieldDoubleList(new RealmList<>(2D, 3D)); obj.setFieldFloatList(new RealmList<>(2F, 3F)); obj.setFieldDateList(new RealmList<>(new Date(2L), new Date(3L))); managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/, managedObj.getFieldIgnored()); assertEquals(fieldStringValue, managedObj.getFieldString()); assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString()); assertEquals(fieldShortValue, managedObj.getFieldShort()); assertEquals(fieldIntValue, managedObj.getFieldInt()); assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); assertEquals(fieldLongValue, managedObj.getFieldLong()); assertEquals(fieldByteValue, managedObj.getFieldByte()); assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0F); assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0D); assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); assertEquals(fieldDateValue, managedObj.getFieldDate()); assertArrayEquals(fieldBinaryValue, managedObj.getFieldBinary()); assertEquals(fieldObjectIntValue, managedObj.getFieldObject().getFieldInt()); assertEquals(1, managedObj.getFieldList().size()); assertEquals(fieldListIntValue, managedObj.getFieldList().first().getFieldInt()); assertEquals(2, managedObj.getFieldStringList().size()); assertEquals(""2"", managedObj.getFieldStringList().get(0)); assertEquals(""3"", managedObj.getFieldStringList().get(1)); assertEquals(2, managedObj.getFieldBooleanList().size()); assertEquals(true, managedObj.getFieldBooleanList().get(0)); assertEquals(false, managedObj.getFieldBooleanList().get(1)); assertEquals(2, managedObj.getFieldBinaryList().size()); assertArrayEquals(new byte[] {2}, managedObj.getFieldBinaryList().get(0)); assertArrayEquals(new byte[] {3}, managedObj.getFieldBinaryList().get(1)); assertEquals(2, managedObj.getFieldLongList().size()); assertEquals((Long) 2L, managedObj.getFieldLongList().get(0)); assertEquals((Long) 3L, managedObj.getFieldLongList().get(1)); assertEquals(2, managedObj.getFieldIntegerList().size()); assertEquals((Integer) 2, managedObj.getFieldIntegerList().get(0)); assertEquals((Integer) 3, managedObj.getFieldIntegerList().get(1)); assertEquals(2, managedObj.getFieldShortList().size()); assertEquals((Short) (short) 2, managedObj.getFieldShortList().get(0)); assertEquals((Short) (short) 3, managedObj.getFieldShortList().get(1)); assertEquals(2, managedObj.getFieldByteList().size()); assertEquals((Byte) (byte) 2, managedObj.getFieldByteList().get(0)); assertEquals((Byte) (byte) 3, managedObj.getFieldByteList().get(1)); assertEquals(2, managedObj.getFieldDoubleList().size()); assertEquals((Double) 2D, managedObj.getFieldDoubleList().get(0)); assertEquals((Double) 3D, managedObj.getFieldDoubleList().get(1)); assertEquals(2, managedObj.getFieldFloatList().size()); assertEquals((Float) 2F, managedObj.getFieldFloatList().get(0)); assertEquals((Float) 3F, managedObj.getFieldFloatList().get(1)); assertEquals(2, managedObj.getFieldDateList().size()); assertEquals(new Date(2L), managedObj.getFieldDateList().get(0)); assertEquals(new Date(3L), managedObj.getFieldDateList().get(1)); // Makes sure that excess object by default value is not created. assertEquals(2, realm.where(RandomPrimaryKey.class).count()); } @Test public void copyFromRealm_defaultValuesAreIgnored() { final DefaultValueOfField managedObj; realm.beginTransaction(); { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + "".modified""); obj.setFieldString(DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + "".modified""); obj.setFieldRandomString(""non-random""); obj.setFieldShort((short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1)); obj.setFieldInt(DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1); obj.setFieldLongPrimaryKey(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1); obj.setFieldLong(DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1); obj.setFieldByte((byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1)); obj.setFieldFloat(DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1); obj.setFieldDouble(DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1); obj.setFieldBoolean(!DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); obj.setFieldDate(new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1)); obj.setFieldBinary(new byte[] {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}); final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); fieldObjectValue.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); obj.setFieldObject(fieldObjectValue); final RealmList list = new RealmList(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2); list.add(listItem); obj.setFieldList(list); obj.setFieldStringList(new RealmList<>(""2"", ""3"")); obj.setFieldBooleanList(new RealmList<>(true, false)); obj.setFieldBinaryList(new RealmList<>(new byte[] {2}, new byte[] {3})); obj.setFieldLongList(new RealmList<>(2L, 3L)); obj.setFieldIntegerList(new RealmList<>(2, 3)); obj.setFieldShortList(new RealmList<>((short) 2, (short) 3)); obj.setFieldByteList(new RealmList<>((byte) 2, (byte) 3)); obj.setFieldDoubleList(new RealmList<>(2D, 3D)); obj.setFieldFloatList(new RealmList<>(2F, 3F)); obj.setFieldDateList(new RealmList<>(new Date(2L), new Date(3L))); managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); final DefaultValueOfField copy = realm.copyFromRealm(managedObj); assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE, copy.getFieldIgnored()); assertEquals(managedObj.getFieldString(), copy.getFieldString()); assertEquals(managedObj.getFieldRandomString(), copy.getFieldRandomString()); assertEquals(managedObj.getFieldShort(), copy.getFieldShort()); assertEquals(managedObj.getFieldInt(), copy.getFieldInt()); assertEquals(managedObj.getFieldLongPrimaryKey(), copy.getFieldLongPrimaryKey()); assertEquals(managedObj.getFieldLong(), copy.getFieldLong()); assertEquals(managedObj.getFieldByte(), copy.getFieldByte()); assertEquals(managedObj.getFieldFloat(), copy.getFieldFloat(), 0F); assertEquals(managedObj.getFieldDouble(), copy.getFieldDouble(), 0D); assertEquals(managedObj.isFieldBoolean(), copy.isFieldBoolean()); assertEquals(managedObj.getFieldDate(), copy.getFieldDate()); assertArrayEquals(managedObj.getFieldBinary(), copy.getFieldBinary()); assertEquals(managedObj.getFieldObject().getFieldInt(), copy.getFieldObject().getFieldInt()); assertEquals(1, copy.getFieldList().size()); //noinspection ConstantConditions assertEquals(managedObj.getFieldList().first().getFieldInt(), copy.getFieldList().first().getFieldInt()); assertEquals(2, managedObj.getFieldStringList().size()); assertEquals(""2"", managedObj.getFieldStringList().get(0)); assertEquals(""3"", managedObj.getFieldStringList().get(1)); assertEquals(2, managedObj.getFieldBooleanList().size()); assertEquals(true, managedObj.getFieldBooleanList().get(0)); assertEquals(false, managedObj.getFieldBooleanList().get(1)); assertEquals(2, managedObj.getFieldBinaryList().size()); assertArrayEquals(new byte[] {2}, managedObj.getFieldBinaryList().get(0)); assertArrayEquals(new byte[] {3}, managedObj.getFieldBinaryList().get(1)); assertEquals(2, managedObj.getFieldLongList().size()); assertEquals((Long) 2L, managedObj.getFieldLongList().get(0)); assertEquals((Long) 3L, managedObj.getFieldLongList().get(1)); assertEquals(2, managedObj.getFieldIntegerList().size()); assertEquals((Integer) 2, managedObj.getFieldIntegerList().get(0)); assertEquals((Integer) 3, managedObj.getFieldIntegerList().get(1)); assertEquals(2, managedObj.getFieldShortList().size()); assertEquals((Short) (short) 2, managedObj.getFieldShortList().get(0)); assertEquals((Short) (short) 3, managedObj.getFieldShortList().get(1)); assertEquals(2, managedObj.getFieldByteList().size()); assertEquals((Byte) (byte) 2, managedObj.getFieldByteList().get(0)); assertEquals((Byte) (byte) 3, managedObj.getFieldByteList().get(1)); assertEquals(2, managedObj.getFieldDoubleList().size()); assertEquals((Double) 2D, managedObj.getFieldDoubleList().get(0)); assertEquals((Double) 3D, managedObj.getFieldDoubleList().get(1)); assertEquals(2, managedObj.getFieldFloatList().size()); assertEquals((Float) 2F, managedObj.getFieldFloatList().get(0)); assertEquals((Float) 3F, managedObj.getFieldFloatList().get(1)); assertEquals(2, managedObj.getFieldDateList().size()); assertEquals(new Date(2L), managedObj.getFieldDateList().get(0)); assertEquals(new Date(3L), managedObj.getFieldDateList().get(1)); } // Tests close Realm in another thread different from where it is created. @Test public void close_differentThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AssertionFailedError threadAssertionError[] = new AssertionFailedError[1]; final Thread thatThread = new Thread(new Runnable() { @Override public void run() { try { realm.close(); threadAssertionError[0] = new AssertionFailedError( ""Close realm in a different thread should throw IllegalStateException.""); } catch (IllegalStateException ignored) { } latch.countDown(); } }); thatThread.start(); // Timeout should never happen. TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } // After exception thrown in another thread, nothing should be changed to the realm in this thread. realm.checkIfValid(); realm.close(); realm = null; } @Test public void isClosed() { assertFalse(realm.isClosed()); realm.close(); assertTrue(realm.isClosed()); } // Tests Realm#isClosed() in another thread different from where it is created. @Test public void isClosed_differentThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AssertionFailedError threadAssertionError[] = new AssertionFailedError[1]; final Thread thatThread = new Thread(new Runnable() { @Override public void run() { try { realm.isClosed(); threadAssertionError[0] = new AssertionFailedError( ""Call isClosed() of Realm instance in a different thread should throw IllegalStateException.""); } catch (IllegalStateException ignored) { } latch.countDown(); } }); thatThread.start(); // Timeout should never happen. TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } // After exception thrown in another thread, nothing should be changed to the realm in this thread. realm.checkIfValid(); assertFalse(realm.isClosed()); realm.close(); } // Realm validation & initialization is done once, still ColumnIndices // should be populated for the subsequent Realm sharing the same configuration // even if we skip initialization & validation. @Test public void columnIndicesIsPopulatedWhenSkippingInitialization() throws Throwable { final RealmConfiguration realmConfiguration = configFactory.createConfiguration(""columnIndices""); final Exception threadError[] = new Exception[1]; final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch mainThreadRealmDone = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfiguration); // This will populate columnIndices. try { bgRealmOpened.countDown(); TestHelper.awaitOrFail(mainThreadRealmDone); realm.close(); bgRealmClosed.countDown(); } catch (Exception e) { threadError[0] = e; } finally { if (!realm.isClosed()) { realm.close(); } } } }).start(); TestHelper.awaitOrFail(bgRealmOpened); Realm realm = Realm.getInstance(realmConfiguration); realm.where(AllTypes.class).equalTo(""columnString"", ""Foo"").findAll(); // This would crash if columnIndices == null. realm.close(); mainThreadRealmDone.countDown(); TestHelper.awaitOrFail(bgRealmClosed); if (threadError[0] != null) { throw threadError[0]; } } @Test public void isInTransaction() { assertFalse(realm.isInTransaction()); realm.beginTransaction(); assertTrue(realm.isInTransaction()); realm.commitTransaction(); assertFalse(realm.isInTransaction()); realm.beginTransaction(); assertTrue(realm.isInTransaction()); realm.cancelTransaction(); assertFalse(realm.isInTransaction()); } // Test for https://github.com/realm/realm-java/issues/1646 @Test public void closingRealmWhileOtherThreadIsOpeningRealm() throws Exception { final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch endLatch = new CountDownLatch(1); final List exception = new ArrayList(); new Thread() { @Override public void run() { try { startLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); return; } final Realm realm = Realm.getInstance(realmConfig); try { realm.where(AllTypes.class).equalTo(""columnLong"", 0L).findFirst(); } catch (Exception e) { exception.add(e); } finally { endLatch.countDown(); realm.close(); } } }.start(); // Prevents for another thread to enter Realm.createAndValidate(). synchronized (BaseRealm.class) { startLatch.countDown(); // Waits for another thread's entering Realm.createAndValidate(). SystemClock.sleep(100L); realm.close(); realm = null; } TestHelper.awaitOrFail(endLatch); if (!exception.isEmpty()) { throw exception.get(0); } } // Bug reported https://github.com/realm/realm-java/issues/1728. // Root cause is validatedRealmFiles will be cleaned when any thread's Realm ref counter reach 0. @Test public void openRealmWhileTransactionInAnotherThread() throws Exception { final CountDownLatch realmOpenedInBgLatch = new CountDownLatch(1); final CountDownLatch realmClosedInFgLatch = new CountDownLatch(1); final CountDownLatch transBeganInBgLatch = new CountDownLatch(1); final CountDownLatch fgFinishedLatch = new CountDownLatch(1); final CountDownLatch bgFinishedLatch = new CountDownLatch(1); final List exception = new ArrayList(); // Step 1: testRealm is opened already. Thread thread = new Thread(new Runnable() { @Override public void run() { // Step 2: Opens realm in background thread. Realm realm = Realm.getInstance(realmConfig); realmOpenedInBgLatch.countDown(); try { realmClosedInFgLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); realm.close(); return; } // Step 4: Starts transaction in background. realm.beginTransaction(); transBeganInBgLatch.countDown(); try { fgFinishedLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); } // Step 6: Cancels Transaction and closes realm in background. realm.cancelTransaction(); realm.close(); bgFinishedLatch.countDown(); } }); thread.start(); TestHelper.awaitOrFail(realmOpenedInBgLatch); // Step 3: Closes all realm instances in foreground thread. realm.close(); realmClosedInFgLatch.countDown(); TestHelper.awaitOrFail(transBeganInBgLatch); // Step 5: Gets a new Realm instance in foreground. realm = Realm.getInstance(realmConfig); fgFinishedLatch.countDown(); TestHelper.awaitOrFail(bgFinishedLatch); if (!exception.isEmpty()) { throw exception.get(0); } } @Test public void isEmpty() { RealmConfiguration realmConfig = configFactory.createConfiguration(""empty_test.realm""); Realm emptyRealm = Realm.getInstance(realmConfig); assertTrue(emptyRealm.isEmpty()); emptyRealm.beginTransaction(); PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); emptyRealm.copyToRealm(obj); assertFalse(emptyRealm.isEmpty()); emptyRealm.cancelTransaction(); assertTrue(emptyRealm.isEmpty()); emptyRealm.beginTransaction(); obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName(""Foo""); emptyRealm.copyToRealm(obj); emptyRealm.commitTransaction(); assertFalse(emptyRealm.isEmpty()); emptyRealm.close(); } @Test public void copyFromRealm_invalidObjectThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); obj.deleteFromRealm(); realm.commitTransaction(); try { realm.copyFromRealm(obj); fail(); } catch (IllegalArgumentException ignored) { } try { realm.copyFromRealm(new AllTypes()); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void copyFromRealm_invalidDepthThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(obj, -1); } @Test public void copyFromRealm() { populateTestRealm(); AllTypes realmObject = realm.where(AllTypes.class).sort(""columnLong"").findAll().first(); AllTypes unmanagedObject = realm.copyFromRealm(realmObject); assertArrayEquals(realmObject.getColumnBinary(), unmanagedObject.getColumnBinary()); assertEquals(realmObject.getColumnString(), unmanagedObject.getColumnString()); assertEquals(realmObject.getColumnLong(), unmanagedObject.getColumnLong()); assertEquals(realmObject.getColumnFloat(), unmanagedObject.getColumnFloat(), 0.00000000001); assertEquals(realmObject.getColumnDouble(), unmanagedObject.getColumnDouble(), 0.00000000001); assertEquals(realmObject.isColumnBoolean(), unmanagedObject.isColumnBoolean()); assertEquals(realmObject.getColumnDate(), unmanagedObject.getColumnDate()); assertEquals(realmObject.getColumnObjectId(), unmanagedObject.getColumnObjectId()); assertEquals(realmObject.getColumnDecimal128(), unmanagedObject.getColumnDecimal128()); assertEquals(realmObject.getColumnUUID(), unmanagedObject.getColumnUUID()); assertEquals(realmObject.getColumnRealmAny(), unmanagedObject.getColumnRealmAny()); } @Test public void copyFromRealm_newCopyEachTime() { populateTestRealm(); AllTypes realmObject = realm.where(AllTypes.class).sort(""columnLong"").findAll().first(); AllTypes unmanagedObject1 = realm.copyFromRealm(realmObject); AllTypes unmanagedObject2 = realm.copyFromRealm(realmObject); assertFalse(unmanagedObject1 == unmanagedObject2); assertNotSame(unmanagedObject1, unmanagedObject2); } // Tests that the object graph is copied as it is and no extra copies are made. // 1) (A -> B/[B,C]) // 2) (C -> B/[B,A]) // A copy should result in only 3 distinct objects. @Test public void copyFromRealm_cyclicObjectGraph() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); objA.setObject(objB); objC.setObject(objB); objA.getObjects().add(objB); objA.getObjects().add(objC); objC.getObjects().add(objB); objC.getObjects().add(objA); realm.commitTransaction(); CyclicType copyA = realm.copyFromRealm(objA); CyclicType copyB = copyA.getObject(); CyclicType copyC = copyA.getObjects().get(1); assertEquals(""A"", copyA.getName()); assertEquals(""B"", copyB.getName()); assertEquals(""C"", copyC.getName()); // Asserts object equality on the object graph. assertTrue(copyA.getObject() == copyC.getObject()); assertTrue(copyA.getObjects().get(0) == copyC.getObjects().get(0)); assertTrue(copyA == copyC.getObjects().get(1)); assertTrue(copyC == copyA.getObjects().get(1)); } // Tests that for (A -> B -> C) for maxDepth = 1, result is (A -> B -> null). @Test public void copyFromRealm_checkMaxDepth() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); objA.setObject(objB); objC.setObject(objC); objA.getObjects().add(objB); objA.getObjects().add(objC); realm.commitTransaction(); CyclicType copyA = realm.copyFromRealm(objA, 1); assertNull(copyA.getObject().getObject()); } // Tests that depth restriction is calculated from the top-most encountered object, i.e. it is possible for some // objects to exceed the depth limit. // A -> B -> C -> D -> E // A -> D -> E // D is both at depth 1 and 3. For maxDepth = 3, E should still be copied. @Test public void copyFromRealm_sameObjectDifferentDepths() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); CyclicType objD = realm.createObject(CyclicType.class); objD.setName(""D""); CyclicType objE = realm.createObject(CyclicType.class); objE.setName(""E""); objA.setObject(objB); objB.setObject(objC); objC.setObject(objD); objD.setObject(objE); objA.setOtherObject(objD); realm.commitTransaction(); // Object is filled before otherObject. (because of field order - WARNING: Not guaranteed) // This means that the object will be encountered first time at max depth, so E will not be copied. // If the object cache does not handle this, otherObject will be wrong. CyclicType copyA = realm.copyFromRealm(objA, 3); assertEquals(""E"", copyA.getOtherObject().getObject().getName()); } @Test public void copyFromRealm_list_invalidListThrows() { realm.beginTransaction(); AllTypes object = realm.createObject(AllTypes.class); List list = new RealmList(object); object.deleteFromRealm(); realm.commitTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(list); } @Test public void copyFromRealm_emptyList() { RealmResults results = realm.where(AllTypes.class).alwaysFalse().findAll(); List copy = realm.copyFromRealm(results); assertEquals(0, copy.size()); } @Test public void copyFromRealm_list_invalidDepthThrows() { RealmResults results = realm.where(AllTypes.class).findAll(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(results, -1); } // Tests that the same Realm objects in a list result in the same Java in-memory copy. // List: A -> [(B -> C), (B -> C)] should result in only 2 copied objects A and B and not A1, B1, A2, B2 @Test public void copyFromRealm_list_sameElements() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName(""A""); CyclicType objB = realm.createObject(CyclicType.class); objB.setName(""B""); CyclicType objC = realm.createObject(CyclicType.class); objC.setName(""C""); objB.setObject(objC); objA.getObjects().add(objB); objA.getObjects().add(objB); realm.commitTransaction(); List results = realm.copyFromRealm(objA.getObjects()); assertEquals(2, results.size()); assertEquals(""B"", results.get(0).getName()); assertTrue(results.get(0) == results.get(1)); } @Test public void copyFromRealm_dynamicRealmObjectThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { realm.copyFromRealm(dObj); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void copyFromRealm_dynamicRealmListThrows() { DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); dynamicRealm.beginTransaction(); RealmList dynamicList = dynamicRealm.createObject(AllTypes.CLASS_NAME).getList(AllTypes.FIELD_REALMLIST); DynamicRealmObject dObj = dynamicRealm.createObject(Dog.CLASS_NAME); dynamicList.add(dObj); dynamicRealm.commitTransaction(); try { realm.copyFromRealm(dynamicList); fail(); } catch (IllegalArgumentException ignored) { } finally { dynamicRealm.close(); } } // Tests if close can be called from Realm change listener when there is no other listeners. @Test public void closeRealmInChangeListener() { looperThread.runBlocking(() -> { Realm realm = Realm.getInstance(realmConfig); looperThread.closeAfterTest(realm); final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } // Tests if close can be called from Realm change listener when there is a listener on empty Realm Object. @Test public void closeRealmInChangeListenerWhenThereIsListenerOnEmptyObject() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { } }; // Change listener on Realm final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); // Change listener on Empty Object final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); allTypes.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } // Tests if close can be called from Realm change listener when there is an listener on non-empty Realm Object. @Test public void closeRealmInChangeListenerWhenThereIsListenerOnObject() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); final RealmChangeListener dummyListener = new RealmChangeListener() { @Override public void onChange(AllTypes object) { } }; final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 2) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); // Change listener on Realm Object. final AllTypes allTypes = realm.where(AllTypes.class).findFirst(); allTypes.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } // Tests if close can be called from Realm change listener when there is an listener on RealmResults. @Test public void closeRealmInChangeListenerWhenThereIsListenerOnResults() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); final RealmChangeListener> dummyListener = new RealmChangeListener>() { @Override public void onChange(RealmResults object) { } }; final RealmChangeListener listener = new RealmChangeListener() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); looperThread.testComplete(); } } }; realm.addChangeListener(listener); // Change listener on Realm results. RealmResults results = realm.where(AllTypes.class).findAll(); results.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); }); } @Test public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); try { realm.addChangeListener(null); fail(""adding null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { looperThread.testComplete(); } }); } @Test public void addChangeListener_throwOnAddingNullListenerFromNonLooperThread() throws Throwable { TestHelper.executeOnNonLooperThread(new TestHelper.Task() { @Override public void run() throws Exception { final Realm realm = Realm.getInstance(realmConfig); //noinspection TryFinallyCanBeTryWithResources try { realm.addChangeListener(null); fail(""adding null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { realm.close(); } } }); } @Test public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { looperThread.runBlocking(() -> { final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); try { realm.removeChangeListener(null); fail(""removing null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { looperThread.testComplete(); } }); } @Test public void removeChangeListener_throwOnRemovingNullListenerFromNonLooperThread() throws Throwable { TestHelper.executeOnNonLooperThread(new TestHelper.Task() { @Override public void run() throws Exception { final Realm realm = Realm.getInstance(realmConfig); //noinspection TryFinallyCanBeTryWithResources try { realm.removeChangeListener(null); fail(""removing null change listener must throw an exception.""); } catch (IllegalArgumentException ignore) { } finally { realm.close(); } } }); } @Test public void removeChangeListenerThrowExceptionOnWrongThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); Realm realm = Realm.getInstance(realmConfig); Thread thread = new Thread(() -> { try { realm.removeChangeListener(object -> {}); fail(""Should not be able to invoke removeChangeListener""); } catch (IllegalStateException ignored) { } finally { signalTestFinished.countDown(); } }); thread.start(); try { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); realm.close(); } } @Test public void removeAllChangeListenersThrowExceptionOnWrongThreadThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); Realm realm = Realm.getInstance(realmConfig); Thread thread = new Thread(() -> { try { realm.removeAllChangeListeners(); fail(""Should not be able to invoke removeChangeListener""); } catch (IllegalStateException ignored) { } finally { signalTestFinished.countDown(); } }); thread.start(); try { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); realm.close(); } } @Test public void deleteAll() { realm.beginTransaction(); realm.createObject(AllTypes.class); realm.createObject(Owner.class).setCat(realm.createObject(Cat.class)); realm.commitTransaction(); assertEquals(1, realm.where(AllTypes.class).count()); assertEquals(1, realm.where(Owner.class).count()); assertEquals(1, realm.where(Cat.class).count()); realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(AllTypes.class).count()); assertEquals(0, realm.where(Owner.class).count()); assertEquals(0, realm.where(Cat.class).count()); assertTrue(realm.isEmpty()); } // Test for https://github.com/realm/realm-java/issues/5745 @Test public void deleteAll_realmWithMoreTables() { realm.close(); RealmConfiguration config1 = configFactory.createConfigurationBuilder() .name(""deleteAllTest.realm"") .schema(StringOnly.class, StringAndInt.class) .build(); realm = Realm.getInstance(config1); realm.executeTransaction(r -> { r.createObject(StringOnly.class); r.createObject(StringAndInt.class); }); realm.close(); RealmConfiguration config2 = configFactory.createConfigurationBuilder() .name(""deleteAllTest.realm"") .schema(StringOnly.class) .build(); realm = Realm.getInstance(config2); realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertTrue(realm.isEmpty()); realm.close(); // deleteAll() will only delete tables part of the schema, so reopening with the old // should reveal the old data realm = Realm.getInstance(config1); assertFalse(realm.isEmpty()); assertEquals(1, realm.where(StringAndInt.class).count()); } @Test public void waitForChange_emptyDataChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); // Waits in background. final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); realm.beginTransaction(); realm.commitTransaction(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); assertEquals(0, bgRealmWaitForChangeResult.get()); } @Test public void waitForChange_withDataChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); // Waits in background. final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); populateTestRealm(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); assertEquals(TEST_DATA_SIZE, bgRealmWaitForChangeResult.get()); } @Test public void waitForChange_syncBackgroundRealmResults() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmResultSize = new AtomicLong(0); // Wait in background final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); RealmResults results = realm.where(AllTypes.class).findAll(); // First makes sure the results is empty. bgRealmResultSize.set(results.size()); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmResultSize.set(results.size()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); // Background result should be empty. assertEquals(0, bgRealmResultSize.get()); populateTestRealm(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); // Once RealmResults are synchronized after waitForChange, the result size should be what we expect. assertEquals(TEST_DATA_SIZE, bgRealmResultSize.get()); } @Test public void stopWaitForChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(true); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }).start(); TestHelper.awaitOrFail(bgRealmOpened); Thread.sleep(200); bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmChangeResult.get()); } // Tests if waitForChange doesn't blocks once stopWaitForChange has been called before. @Test public void waitForChange_stopWaitForChangeDisablesWaiting() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmStopped = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmFirstWaitResult = new AtomicBoolean(true); final AtomicBoolean bgRealmSecondWaitResult = new AtomicBoolean(false); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmFirstWaitResult.set(realm.waitForChange()); bgRealmStopped.countDown(); bgRealmSecondWaitResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }).start(); TestHelper.awaitOrFail(bgRealmOpened); bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmStopped); assertFalse(bgRealmFirstWaitResult.get()); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmSecondWaitResult.get()); } @Test public void waitForChange_stopWaitForChangeReleasesAllWaitingThreads() throws InterruptedException { final CountDownLatch bgRealmsOpened = new CountDownLatch(2); final CountDownLatch bgRealmsClosed = new CountDownLatch(2); final AtomicBoolean bgRealmFirstWaitResult = new AtomicBoolean(true); final AtomicBoolean bgRealmSecondWaitResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. Thread thread1 = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmsOpened.countDown(); bgRealmFirstWaitResult.set(realm.waitForChange()); realm.close(); bgRealmsClosed.countDown(); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmsOpened.countDown(); bgRealmSecondWaitResult.set(realm.waitForChange());//In Core 6 calling stopWaitForChange will release all waiting threads // which causes query below to run before `populateTestRealm` happens bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmsClosed.countDown(); } }); thread1.start(); thread2.start(); TestHelper.awaitOrFail(bgRealmsOpened); bgRealm.get().stopWaitForChange(); // Waits for Thread 2 to wait. Thread.sleep(500); populateTestRealm(); TestHelper.awaitOrFail(bgRealmsClosed); assertFalse(bgRealmFirstWaitResult.get()); assertFalse(bgRealmSecondWaitResult.get()); assertEquals(0, bgRealmWaitForChangeResult.get()); } // Checks if waitForChange() does not respond to Thread.interrupt(). @Test public void waitForChange_interruptingThread() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicReference bgRealmWaitResult = new AtomicReference(); final AtomicReference bgRealm = new AtomicReference(); // Waits in background. Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmWaitResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); // Makes sure background thread goes to wait. Thread.sleep(500); // Interrupting a thread should neither cause any side effect nor terminate the Background Realm from waiting. thread.interrupt(); assertTrue(thread.isInterrupted()); assertEquals(null, bgRealmWaitResult.get()); // Now we'll stop realm from waiting. bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmWaitResult.get()); } @Test public void waitForChange_onLooperThread() throws Throwable { final CountDownLatch bgRealmClosed = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Realm realm = Realm.getInstance(realmConfig); try { realm.waitForChange(); fail(); } catch (IllegalStateException ignored) { } finally { realm.close(); bgRealmClosed.countDown(); } } }); thread.start(); TestHelper.awaitOrFail(bgRealmClosed); } // Cannot wait inside of a transaction. @Test(expected = IllegalStateException.class) public void waitForChange_illegalWaitInsideTransaction() { realm.beginTransaction(); realm.waitForChange(); } @Test public void waitForChange_stopWaitingOnClosedRealmThrows() throws InterruptedException { final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicReference bgRealm = new AtomicReference(); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmClosed); try { bgRealm.get().stopWaitForChange(); fail(""Cannot stop a closed Realm from waiting""); } catch (IllegalStateException expected) { } } // waitForChange & stopWaitForChange within a simple Thread wrapper. @Test public void waitForChange_runWithRealmThread() throws InterruptedException { final CountDownLatch bgRealmStarted = new CountDownLatch(1); final CountDownLatch bgRealmFished = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmResultSize = new AtomicLong(0); RealmThread thread = new RealmThread(realmConfig, new RealmThread.RealmRunnable() { @Override public void run(Realm realm) { bgRealmStarted.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmResultSize.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmFished.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmStarted); populateTestRealm(); TestHelper.awaitOrFail(bgRealmFished); assertTrue(bgRealmChangeResult.get()); assertEquals(TEST_DATA_SIZE, bgRealmResultSize.get()); } @Test public void waitForChange_endRealmThread() throws InterruptedException { final CountDownLatch bgRealmStarted = new CountDownLatch(1); final CountDownLatch bgRealmFished = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(true); RealmThread thread = new RealmThread(realmConfig, new RealmThread.RealmRunnable() { @Override public void run(Realm realm) { bgRealmStarted.countDown(); bgRealmChangeResult.set(realm.waitForChange()); realm.close(); bgRealmFished.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmStarted); thread.end(); TestHelper.awaitOrFail(bgRealmFished); assertFalse(bgRealmChangeResult.get()); } @Test public void getGlobalInstanceCount() { final CountDownLatch bgDone = new CountDownLatch(1); final RealmConfiguration config = configFactory.createConfiguration(""globalCountTest""); assertEquals(0, Realm.getGlobalInstanceCount(config)); // Opens thread local Realm. Realm realm = Realm.getInstance(config); assertEquals(1, Realm.getGlobalInstanceCount(config)); Realm realm1 = Realm.getInstance(config); assertEquals(1, Realm.getGlobalInstanceCount(config)); // Even though each Realm type points to the same Realm on disk, we report them as // multiple global instances // Opens thread local DynamicRealm. DynamicRealm dynRealm = DynamicRealm.getInstance(config); assertEquals(2, Realm.getGlobalInstanceCount(config)); // Create frozen Realms. Realm frozenRealm = realm.freeze(); assertTrue(frozenRealm.isFrozen()); assertEquals(3, Realm.getGlobalInstanceCount(config)); DynamicRealm frozenDynamicRealm = dynRealm.freeze(); assertTrue(frozenDynamicRealm.isFrozen()); assertEquals(4, Realm.getGlobalInstanceCount(config)); // Opens Realm in another thread. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(config); assertEquals(5, Realm.getGlobalInstanceCount(config)); realm.close(); assertEquals(4, Realm.getGlobalInstanceCount(config)); bgDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgDone); dynRealm.close(); assertEquals(3, Realm.getGlobalInstanceCount(config)); realm.close(); realm1.close(); // Fully closing the live Realm also closes all frozen Realms assertEquals(0, Realm.getGlobalInstanceCount(config)); assertTrue(frozenRealm.isClosed()); assertTrue(frozenDynamicRealm.isClosed()); } @Test public void getLocalInstanceCount() { final RealmConfiguration config = configFactory.createConfiguration(""localInstanceCount""); assertEquals(0, Realm.getLocalInstanceCount(config)); // Opens thread local Realm. Realm realm = Realm.getInstance(config); assertEquals(1, Realm.getLocalInstanceCount(config)); // Opens thread local DynamicRealm. DynamicRealm dynRealm = DynamicRealm.getInstance(config); assertEquals(2, Realm.getLocalInstanceCount(config)); dynRealm.close(); assertEquals(1, Realm.getLocalInstanceCount(config)); realm.close(); assertEquals(0, Realm.getLocalInstanceCount(config)); } @Test public void namedPipeDirForExternalStorage() { // Test for https://github.com/realm/realm-java/issues/3140 realm.close(); realm = null; final File namedPipeDir = OsSharedRealm.getTemporaryDirectory(); assertTrue(namedPipeDir.isDirectory()); TestHelper.deleteRecursively(namedPipeDir); //noinspection ResultOfMethodCallIgnored namedPipeDir.mkdirs(); final File externalFilesDir = context.getExternalFilesDir(null); final RealmConfiguration config = configFactory.createConfigurationBuilder() .directory(externalFilesDir) .name(""external.realm"") .build(); Realm.deleteRealm(config); // Test if it works when the namedPipeDir is empty. Realm realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); assertTrue(namedPipeDir.isDirectory()); Assume.assumeTrue(""SELinux is not enforced on this device."", TestHelper.isSelinuxEnforcing()); // Only checks the fifo file created by call, since all Realm instances share the same fifo created by // external_commit_helper which might not be created in the newly created dir if there are Realm instances // are not deleted when TestHelper.deleteRecursively(namedPipeDir) called. File[] files = namedPipeDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.matches(""realm_.*cv""); } }); assertEquals(2, files.length); // Tests if it works when the namedPipeDir and the named pipe files already exist. realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); } @Test(expected = IllegalStateException.class) public void getInstanceAsync_nonLooperThreadShouldThrow() { Realm.getInstanceAsync(realmConfig, new Realm.Callback() { @Override public void onSuccess(Realm realm) { fail(); } }); } @Test public void getInstanceAsync_nullConfigShouldThrow() { looperThread.runBlocking(() -> { try { Realm.getInstanceAsync(null, new Realm.Callback() { @Override public void onSuccess(Realm realm) { fail(); } }); fail(); } catch(IllegalArgumentException ignore) { } looperThread.testComplete(); }); } // Verify that the logic for waiting for the users file dir to be come available isn't totally broken // This is pretty hard to test, so forced to break encapsulation in this case. @Test public void init_waitForFilesDir() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { java.lang.reflect.Method m = Realm.class.getDeclaredMethod(""checkFilesDirAvailable"", Context.class); m.setAccessible(true); // A) Check it fails if getFilesDir is never created Context mockContext = mock(Context.class); when(mockContext.getFilesDir()).thenReturn(null); try { m.invoke(null, mockContext); fail(); } catch (InvocationTargetException e) { assertEquals(IllegalStateException.class, e.getCause().getClass()); } // B) Check we return if the filesDir becomes available after a while mockContext = mock(Context.class); when(mockContext.getFilesDir()).then(new Answer() { int calls = 0; File userFolder = tmpFolder.newFolder(); @Override public File answer(InvocationOnMock invocationOnMock) throws Throwable { calls++; return (calls > 5) ? userFolder : null; // Start returning the correct folder after 5 attempts } }); assertNull(m.invoke(null, mockContext)); } @Test public void refresh_triggerNotifications() { looperThread.runBlocking(() -> { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(0, results.size()); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { assertEquals(1, results.size()); listenerCalled.set(true); } }); // Advance the Realm on a background while blocking this thread. When we refresh, it should trigger // the listener. new Thread(() -> { Realm realm1 = Realm.getInstance(realmConfig); realm1.beginTransaction(); realm1.createObject(AllTypes.class); realm1.commitTransaction(); realm1.close(); bgThreadDone.countDown(); }).start(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertTrue(listenerCalled.get()); looperThread.testComplete(); }); } @Test public void refresh_nonLooperThreadAdvances() { final CountDownLatch bgThreadDone = new CountDownLatch(1); RealmResults results = realm.where(AllTypes.class).findAll(); assertEquals(0, results.size()); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(RealmTests.this.realm.getConfiguration()); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); realm.close(); bgThreadDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertEquals(1, results.size()); } @Test public void refresh_forceSynchronousNotifications() { looperThread.runBlocking(() -> { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); final Realm realm = Realm.getInstance((realmConfig)); looperThread.closeAfterTest(realm); RealmResults results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(new RealmChangeListener>() { @Override public void onChange(RealmResults results) { // Will be forced synchronous assertEquals(1, results.size()); listenerCalled.set(true); } }); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); realm.close(); bgThreadDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertTrue(listenerCalled.get()); looperThread.testComplete(); }); } @Test public void refresh_insideTransactionThrows() { realm.beginTransaction(); try { realm.refresh(); fail(); } catch (IllegalStateException ignored) { } realm.cancelTransaction(); } @Test public void beginTransaction_readOnlyThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""readonly.realm"") .schema(StringOnlyReadOnly.class) .assetFile(""readonly.realm"") .readOnly() .build(); Realm realm = Realm.getInstance(config); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage(), CoreMatchers.containsString(""Can't perform transactions on read-only Realms."")); } finally { realm.close(); } } @Test public void getInstance_wrongSchemaInReadonlyThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""readonly.realm"") .schema(StringOnlyReadOnly.class, AllJavaTypes.class) .assetFile(""readonly.realm"") .readOnly() .build(); // This will throw because the Realm doesn't have the correct schema, and a new file cannot be re-created // because it is read only. try { realm = Realm.getInstance(config); fail(); } catch (RealmMigrationNeededException ignored) { } } // https://github.com/realm/realm-java/issues/5570 @Test public void getInstance_migrationExceptionThrows_migrationBlockDefiend_realmInstancesShouldBeClosed() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""readonly.realm"") .schema(StringOnlyReadOnly.class, AllJavaTypes.class) .schemaVersion(2) .assetFile(""readonly.realm"") .migration(new RealmMigration() { @Override public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { } }) .build(); try { realm = Realm.getInstance(config); fail(); } catch (RealmMigrationNeededException ignored) { // No Realm instance should be opened at this time. Realm.deleteRealm(config); } } @Test public void hittingMaxNumberOfVersionsThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""versions-test.realm"") .maxNumberOfActiveVersions(1) .build(); Realm realm = Realm.getInstance(config); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertTrue(e.getMessage().contains(""Number of active versions (2) in the Realm exceeded the limit of 1"")); } finally { realm.close(); } } // Test for https://github.com/realm/realm-java/issues/6977 @Test public void numberOfVersionsDecreasedOnClose() { realm.close(); looperThread.runBlocking(() -> { int count = 50; final CountDownLatch bgThreadDoneLatch = new CountDownLatch(count); RealmConfiguration config = configFactory.createConfigurationBuilder() // The multiple embedded threads seems to cause trouble with factory's directory setting .directory(context.getFilesDir()) .name(""versions-test.realm"") .maxNumberOfActiveVersions(5) .build(); Realm.deleteRealm(config); // Synchronizes between change listener and Background writes so they operate in lockstep. AtomicReference guard = new AtomicReference<>(new CountDownLatch(1)); Realm realm = Realm.getInstance(config); looperThread.closeAfterTest(realm); realm.addChangeListener(callbackRealm -> { // This test catches a bug that caused ObjectStore to pin Realm versions // if a TableView was created inside a change notification and no elements // in the TableView was accessed. RealmResults query = realm.where(AllJavaTypes.class).findAll(); guard.get().countDown(); bgThreadDoneLatch.countDown(); if (bgThreadDoneLatch.getCount() == 0) { looperThread.testComplete(); } }); // Write a number of transactions in the background in a serial manner // in order to create a number of different versions. Done in serial // to allow the LooperThread to catch up. new Thread(() -> { for (int i = 0; i < count; i++) { Thread t = new Thread() { @Override public void run() { Realm realm = Realm.getInstance(config); realm.executeTransaction(bgRealm -> { }); realm.close(); } }; t.start(); try { t.join(); TestHelper.awaitOrFail(guard.get()); guard.set(new CountDownLatch(1)); } catch (InterruptedException e) { throw new RuntimeException(e); } } }).start(); }); } // Test for https://github.com/realm/realm-java/issues/6152 @Test @Ignore(""See https://github.com/realm/realm-java/issues/7628"") public void encryption_stressTest() { realm.close(); looperThread.runBlocking(() -> { final int WRITER_TRANSACTIONS = 50; final int TEST_OBJECTS = 100_000; final int MAX_STRING_LENGTH = 1000; final AtomicInteger id = new AtomicInteger(0); long seed = System.nanoTime(); Random random = new Random(seed); RealmConfiguration config = configFactory.createConfigurationBuilder() .name(""encryption-stress-test.realm"") .encryptionKey(TestHelper.getRandomKey(seed)) .build(); Realm.deleteRealm(config); Thread t = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(config); for (int i = 0; i < WRITER_TRANSACTIONS; i++) { realm.executeTransaction(r -> { for (int j = 0; j < (TEST_OBJECTS / WRITER_TRANSACTIONS); j++) { AllJavaTypes obj = new AllJavaTypes(id.incrementAndGet()); obj.setFieldString(TestHelper.getRandomString(random.nextInt(MAX_STRING_LENGTH))); r.insert(obj); } }); } realm.close(); } }); t.start(); Realm realm = Realm.getInstance(config); looperThread.closeAfterTest(realm); RealmResults results = realm.where(AllJavaTypes.class).findAllAsync(); looperThread.keepStrongReference(results); results.addChangeListener(new OrderedRealmCollectionChangeListener>() { @Override public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) { for (AllJavaTypes obj : results) { String s = obj.getFieldString(); } if (results.size() == TEST_OBJECTS) { try { t.join(5000); } catch (InterruptedException e) { fail(""workerthread failed to finish in time.""); } looperThread.testComplete(); } } }); }); } @Test public void getNumberOfActiveVersions() throws InterruptedException { CountDownLatch bgWritesCompleted = new CountDownLatch(1); CountDownLatch closeBgRealm = new CountDownLatch(1); assertEquals(2, realm.getNumberOfActiveVersions()); Thread t = new Thread(() -> { Realm bgRealm = Realm.getInstance(realmConfig); assertEquals(2, bgRealm.getNumberOfActiveVersions()); for (int i = 0; i < 5; i++) { bgRealm.executeTransaction(r -> { /* empty */ }); } assertEquals(6, bgRealm.getNumberOfActiveVersions()); bgWritesCompleted.countDown(); TestHelper.awaitOrFail(closeBgRealm); bgRealm.close(); }); t.start(); TestHelper.awaitOrFail(bgWritesCompleted); assertEquals(6, realm.getNumberOfActiveVersions()); closeBgRealm.countDown(); t.join(); realm.refresh(); // Release old versions for GC realm.beginTransaction(); realm.commitTransaction(); // Actually release the versions assertEquals(2, realm.getNumberOfActiveVersions()); realm.close(); try { realm.getNumberOfActiveVersions(); fail(); } catch (IllegalStateException ignore) { } } @Test public void getCachedInstanceDoNotTriggerStrictMode() { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyDeath() .build()); try { Realm.getInstance(realmConfig).close(); } finally { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .permitAll() .build()); } } @Test public void getCachedInstanceFromOtherThreadDoNotTriggerStrictMode() throws InterruptedException { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .penaltyDeath() .build()); try { Thread t = new Thread(() -> Realm.getInstance(realmConfig).close()); t.start(); t.join(); } finally { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .permitAll() .build()); } } } ","compactOnLaunchCount " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ /* * Copyright 2010 Mario Zechner (contact@badlogicgames.com), Nathan Sweet (admin@esotericsoftware.com) * * Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an ""AS IS"" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package com.badlogic.gdx.tests.box2d; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.Body; import com.badlogic.gdx.physics.box2d.BodyDef; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; import com.badlogic.gdx.physics.box2d.CircleShape; import com.badlogic.gdx.physics.box2d.ContactFilter; import com.badlogic.gdx.physics.box2d.EdgeShape; import com.badlogic.gdx.physics.box2d.Fixture; import com.badlogic.gdx.physics.box2d.PolygonShape; import com.badlogic.gdx.physics.box2d.World; public class OneSidedPlatform extends Box2DTest { enum State { Unknown, Above, Below } Fixture m_platform; Fixture m_character; float m_bottom; float m_top; float m_radius; State m_state; @Override protected void createWorld (World [MASK] ) { { BodyDef bd = new BodyDef(); Body ground = [MASK] .createBody(bd); EdgeShape shape = new EdgeShape(); shape.set(new Vector2(-20.0f, 0), new Vector2(20.0f, 0f)); ground.createFixture(shape, 0); shape.dispose(); } { BodyDef bd = new BodyDef(); bd.position.set(0, 10); Body body = [MASK] .createBody(bd); PolygonShape shape = new PolygonShape(); shape.setAsBox(3, 0.5f); m_platform = body.createFixture(shape, 0); m_bottom = 10.0f - 0.5f; m_top = 10.0f + 0.5f; } { BodyDef bd = new BodyDef(); bd.type = BodyType.DynamicBody; bd.position.set(0, 12); Body body = [MASK] .createBody(bd); m_radius = 0.5f; CircleShape shape = new CircleShape(); shape.setRadius(m_radius); m_character = body.createFixture(shape, 20.0f); shape.dispose(); m_state = State.Unknown; } [MASK] .setContactFilter(new ContactFilter() { @Override public boolean shouldCollide (Fixture fixtureA, Fixture fixtureB) { if ((fixtureA == m_platform && fixtureB == m_character) || (fixtureB == m_platform && fixtureA == m_character)) { Vector2 position = m_character.getBody().getPosition(); if (position.y < m_top + m_radius - 3.0f * 0.005f) return false; else return true; } else return true; } }); } } ","world " "/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.compression; import static io.netty.handler.codec.compression.Bzip2Constants.HUFFMAN_DECODE_MAX_CODE_LENGTH; import static io.netty.handler.codec.compression.Bzip2Constants.HUFFMAN_GROUP_RUN_LENGTH; import static io.netty.handler.codec.compression.Bzip2Constants.HUFFMAN_MAX_ALPHABET_SIZE; /** * A decoder for the Bzip2 Huffman coding stage. */ final class Bzip2HuffmanStageDecoder { /** * A reader that provides bit-level reads. */ private final Bzip2BitReader reader; /** * The Huffman table number to use for each group of 50 symbols. */ byte[] selectors; /** * The minimum code length for each Huffman table. */ private final int[] minimumLengths; /** * An array of values for each Huffman table that must be subtracted from the numerical value of * a Huffman code of a given bit length to give its canonical code index. */ private final int[][] codeBases; /** * An array of values for each Huffman table that gives the highest numerical value of a Huffman * code of a given bit length. */ private final int[][] codeLimits; /** * A mapping for each Huffman table from canonical code index to output symbol. */ private final int[][] codeSymbols; /** * The Huffman table for the current group. */ private int currentTable; /** * The index of the current group within the selectors array. */ private int groupIndex = -1; /** * The byte position within the current group. A new group is selected every 50 decoded bytes. */ private int groupPosition = -1; /** * Total number of used Huffman tables in range 2..6. */ final int totalTables; /** * The total number of codes (uniform for each table). */ final int alphabetSize; /** * Table for Move To Front transformations. */ final Bzip2MoveToFrontTable tableMTF = new Bzip2MoveToFrontTable(); // For saving state if end of current ByteBuf was reached int currentSelector; /** * The Canonical Huffman code lengths for each table. */ final byte[][] tableCodeLengths; // For saving state if end of current ByteBuf was reached int currentGroup; int currentLength = -1; int currentAlpha; boolean modifyLength; Bzip2HuffmanStageDecoder(final Bzip2BitReader reader, final int totalTables, final int alphabetSize) { this.reader = reader; this.totalTables = totalTables; this.alphabetSize = alphabetSize; minimumLengths = new int[totalTables]; codeBases = new int[totalTables][HUFFMAN_DECODE_MAX_CODE_LENGTH + 2]; codeLimits = new int[totalTables][HUFFMAN_DECODE_MAX_CODE_LENGTH + 1]; codeSymbols = new int[totalTables][HUFFMAN_MAX_ALPHABET_SIZE]; tableCodeLengths = new byte[totalTables][HUFFMAN_MAX_ALPHABET_SIZE]; } /** * Constructs Huffman decoding tables from lists of Canonical Huffman code lengths. */ void createHuffmanDecodingTables() { final int alphabetSize = this.alphabetSize; for (int table = 0; table < tableCodeLengths.length; table++) { final int[] tableBases = codeBases[table]; final int[] tableLimits = codeLimits[table]; final int[] tableSymbols = codeSymbols[table]; final byte[] codeLengths = tableCodeLengths[table]; int minimumLength = HUFFMAN_DECODE_MAX_CODE_LENGTH; int [MASK] = 0; // Find the minimum and maximum code length for the table for (int i = 0; i < alphabetSize; i++) { final byte currLength = codeLengths[i]; [MASK] = Math.max(currLength, [MASK] ); minimumLength = Math.min(currLength, minimumLength); } minimumLengths[table] = minimumLength; // Calculate the first output symbol for each code length for (int i = 0; i < alphabetSize; i++) { tableBases[codeLengths[i] + 1]++; } for (int i = 1, b = tableBases[0]; i < HUFFMAN_DECODE_MAX_CODE_LENGTH + 2; i++) { b += tableBases[i]; tableBases[i] = b; } // Calculate the first and last Huffman code for each code length (codes at a given // length are sequential in value) for (int i = minimumLength, code = 0; i <= [MASK] ; i++) { int base = code; code += tableBases[i + 1] - tableBases[i]; tableBases[i] = base - tableBases[i]; tableLimits[i] = code - 1; code <<= 1; } // Populate the mapping from canonical code index to output symbol for (int bitLength = minimumLength, codeIndex = 0; bitLength <= [MASK] ; bitLength++) { for (int symbol = 0; symbol < alphabetSize; symbol++) { if (codeLengths[symbol] == bitLength) { tableSymbols[codeIndex++] = symbol; } } } } currentTable = selectors[0]; } /** * Decodes and returns the next symbol. * @return The decoded symbol */ int nextSymbol() { // Move to next group selector if required if (++groupPosition % HUFFMAN_GROUP_RUN_LENGTH == 0) { groupIndex++; if (groupIndex == selectors.length) { throw new DecompressionException(""error decoding block""); } currentTable = selectors[groupIndex] & 0xff; } final Bzip2BitReader reader = this.reader; final int currentTable = this.currentTable; final int[] tableLimits = codeLimits[currentTable]; final int[] tableBases = codeBases[currentTable]; final int[] tableSymbols = codeSymbols[currentTable]; int codeLength = minimumLengths[currentTable]; // Starting with the minimum bit length for the table, read additional bits one at a time // until a complete code is recognised int codeBits = reader.readBits(codeLength); for (; codeLength <= HUFFMAN_DECODE_MAX_CODE_LENGTH; codeLength++) { if (codeBits <= tableLimits[codeLength]) { // Convert the code to a symbol index and return return tableSymbols[codeBits - tableBases[codeLength]]; } codeBits = codeBits << 1 | reader.readBits(1); } throw new DecompressionException(""a valid code was not recognised""); } } ","maximumLength " "/******************************************************************************* * Copyright 2022 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.backends.gwt; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.utils.GdxRuntimeException; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArrayBoolean; import com.google.gwt.core.client.JsArrayInteger; import com.google.gwt.typedarrays.client.Uint8ArrayNative; import com.google.gwt.typedarrays.shared.ArrayBufferView; import com.google.gwt.typedarrays.shared.TypedArrays; import com.google.gwt.typedarrays.shared.Uint32Array; import com.google.gwt.webgl.client.WebGL2RenderingContext; import com.google.gwt.webgl.client.WebGLFramebuffer; import com.google.gwt.webgl.client.WebGLQuery; import com.google.gwt.webgl.client.WebGLSampler; import com.google.gwt.webgl.client.WebGLTexture; import com.google.gwt.webgl.client.WebGLTransformFeedback; import com.google.gwt.webgl.client.WebGLUniformLocation; import com.google.gwt.webgl.client.WebGLVertexArrayObject; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.HasArrayBufferView; import java.nio.IntBuffer; import java.nio.LongBuffer; /** @author Simon Gerst * @author JamesTKhan */ public class GwtGL30 extends GwtGL20 implements GL30 { private final IntMap queries = IntMap.create(); private int nextQueryId = 1; private final IntMap samplers = IntMap.create(); private int nextSamplerId = 1; private final IntMap feedbacks = IntMap.create(); private int nextFeedbackId = 1; private final IntMap vertexArrays = IntMap.create(); private int nextVertexArrayId = 1; private final Uint32Array uIntBuffer = TypedArrays.createUint32Array(2000 * 6); final protected WebGL2RenderingContext gl; protected GwtGL30 (WebGL2RenderingContext gl) { super(gl); this.gl = gl; } private Uint32Array copyUnsigned (IntBuffer buffer) { if (GWT.isProdMode()) { return ((Uint32Array)((HasArrayBufferView)buffer).getTypedArray()).subarray(buffer.position(), buffer.remaining()); } else { ensureCapacity(buffer); for (int i = buffer.position(), j = 0; i < buffer.limit(); i++, j++) { uIntBuffer.set(j, buffer.get(i)); } return uIntBuffer.subarray(0, buffer.remaining()); } } private int allocateQueryId (WebGLQuery query) { int id = nextQueryId++; queries.put(id, query); return id; } private void deallocateQueryId (int id) { queries.remove(id); } private int allocateSamplerId (WebGLSampler query) { int id = nextSamplerId++; samplers.put(id, query); return id; } private void deallocateFeedbackId (int id) { feedbacks.remove(id); } private int allocateFeedbackId (WebGLTransformFeedback feedback) { int id = nextFeedbackId++; feedbacks.put(id, feedback); return id; } private void deallocateSamplerId (int id) { samplers.remove(id); } private int allocateVertexArrayId (WebGLVertexArrayObject vArray) { int id = nextVertexArrayId++; vertexArrays.put(id, vArray); return id; } private void deallocateVertexArrayId (int id) { vertexArrays.remove(id); } @Override public void glBeginQuery (int target, int id) { gl.beginQuery(target, queries.get(id)); } @Override public void glBeginTransformFeedback (int primitiveMode) { gl.beginTransformFeedback(primitiveMode); } @Override public void glBindBufferBase (int target, int index, int buffer) { gl.bindBufferBase(target, index, buffers.get(buffer)); } @Override public void glBindBufferRange (int target, int index, int buffer, int offset, int size) { gl.bindBufferRange(target, index, buffers.get(buffer), offset, size); } @Override public void glBindSampler (int unit, int sampler) { gl.bindSampler(unit, samplers.get(sampler)); } @Override public void glBindTransformFeedback (int target, int id) { gl.bindTransformFeedback(target, feedbacks.get(id)); } @Override public void glBindVertexArray (int array) { gl.bindVertexArray(vertexArrays.get(array)); } @Override public void glBlitFramebuffer (int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { gl.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } @Override public void glClearBufferfi (int buffer, int drawbuffer, float depth, int stencil) { gl.clearBufferfi(buffer, drawbuffer, depth, stencil); } @Override public void glClearBufferfv (int buffer, int drawbuffer, FloatBuffer value) { gl.clearBufferfv(buffer, drawbuffer, copy(value)); } @Override public void glClearBufferiv (int buffer, int drawbuffer, IntBuffer value) { gl.clearBufferiv(buffer, drawbuffer, copy(value)); } @Override public void glClearBufferuiv (int buffer, int drawbuffer, IntBuffer value) { gl.clearBufferuiv(buffer, drawbuffer, copy(value)); } @Override public void glCopyBufferSubData (int readTarget, int writeTarget, int readOffset, int writeOffset, int size) { gl.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); } @Override public void glCopyTexSubImage3D (int target, int level, int xoffset, int yoffset, int zoffset, int x, int y, int width, int height) { gl.copyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); } @Override public void glDeleteQueries (int n, int[] ids, int offset) { for (int i = offset; i < offset + n; i++) { int id = ids[i]; WebGLQuery query = queries.get(id); deallocateQueryId(id); gl.deleteQuery(query); } } @Override public void glDeleteQueries (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { int id = ids.get(); WebGLQuery query = queries.get(id); deallocateQueryId(id); gl.deleteQuery(query); } ids.position(startPosition); } @Override public void glDeleteSamplers (int count, int[] samplers, int offset) { for (int i = offset; i < offset + count; i++) { int id = samplers[i]; WebGLSampler sampler = this.samplers.get(id); deallocateSamplerId(id); gl.deleteSampler(sampler); } } @Override public void glDeleteSamplers (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { int id = ids.get(); WebGLSampler sampler = samplers.get(id); deallocateSamplerId(id); gl.deleteSampler(sampler); } ids.position(startPosition); } @Override public void glDeleteTransformFeedbacks (int n, int[] ids, int offset) { for (int i = offset; i < offset + n; i++) { int id = ids[i]; WebGLTransformFeedback feedback = feedbacks.get(id); deallocateFeedbackId(id); gl.deleteTransformFeedback(feedback); } } @Override public void glDeleteTransformFeedbacks (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { int id = ids.get(); WebGLTransformFeedback feedback = feedbacks.get(id); deallocateFeedbackId(id); gl.deleteTransformFeedback(feedback); } ids.position(startPosition); } @Override public void glDeleteVertexArrays (int n, int[] arrays, int offset) { for (int i = offset; i < offset + n; i++) { int id = arrays[i]; WebGLVertexArrayObject vArray = vertexArrays.get(id); deallocateVertexArrayId(id); gl.deleteVertexArray(vArray); } } @Override public void glDeleteVertexArrays (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { int id = ids.get(); WebGLVertexArrayObject vArray = vertexArrays.get(id); deallocateVertexArrayId(id); gl.deleteVertexArray(vArray); } ids.position(startPosition); } @Override public void glDrawArraysInstanced (int mode, int first, int count, int instanceCount) { gl.drawArraysInstanced(mode, first, count, instanceCount); } @Override public void glDrawBuffers (int n, IntBuffer bufs) { int startPosition = bufs.position(); gl.drawBuffers(copy((IntBuffer)bufs).subarray(0, n)); bufs.position(startPosition); } @Override public void glDrawElementsInstanced (int mode, int count, int type, int indicesOffset, int instanceCount) { gl.drawElementsInstanced(mode, count, type, indicesOffset, instanceCount); } @Override public void glDrawRangeElements (int mode, int start, int end, int count, int type, Buffer indices) { gl.drawRangeElements(mode, start, end, count, type, indices.position()); } @Override public void glDrawRangeElements (int mode, int start, int end, int count, int type, int offset) { gl.drawRangeElements(mode, start, end, count, type, offset); } @Override public void glTexImage2D (int target, int level, int internalformat, int width, int height, int border, int format, int type, int offset) { gl.texImage2D(target, level, internalformat, width, height, border, format, type, offset); } @Override public void glEndQuery (int target) { gl.endQuery(target); } @Override public void glEndTransformFeedback () { gl.endTransformFeedback(); } @Override public void glFlushMappedBufferRange (int target, int offset, int length) { throw new UnsupportedOperationException(""glFlushMappedBufferRange not supported on WebGL2""); } @Override public void glFramebufferTextureLayer (int target, int attachment, int texture, int level, int layer) { gl.framebufferTextureLayer(target, attachment, textures.get(texture), level, layer); } @Override public void glGenQueries (int n, int[] ids, int offset) { for (int i = offset; i < offset + n; i++) { WebGLQuery query = gl.createQuery(); int id = allocateQueryId(query); ids[i] = id; } } @Override public void glGenQueries (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { WebGLQuery query = gl.createQuery(); int id = allocateQueryId(query); ids.put(id); } ids.position(startPosition); } @Override public void glGenSamplers (int count, int[] samplers, int offset) { for (int i = offset; i < offset + count; i++) { WebGLSampler sampler = gl.createSampler(); int id = allocateSamplerId(sampler); samplers[i] = id; } } @Override public void glGenSamplers (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { WebGLSampler sampler = gl.createSampler(); int id = allocateSamplerId(sampler); ids.put(id); } ids.position(startPosition); } @Override public void glGenTransformFeedbacks (int n, int[] ids, int offset) { for (int i = offset; i < offset + n; i++) { WebGLTransformFeedback feedback = gl.createTransformFeedback(); int id = allocateFeedbackId(feedback); ids[i] = id; } } @Override public void glGenTransformFeedbacks (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { WebGLTransformFeedback feedback = gl.createTransformFeedback(); int id = allocateFeedbackId(feedback); ids.put(id); } ids.position(startPosition); } @Override public void glGenVertexArrays (int n, int[] arrays, int offset) { for (int i = offset; i < offset + n; i++) { WebGLVertexArrayObject vArray = gl.createVertexArray(); int id = allocateVertexArrayId(vArray); arrays[i] = id; } } @Override public void glGenVertexArrays (int n, IntBuffer ids) { int startPosition = ids.position(); for (int i = 0; i < n; i++) { WebGLVertexArrayObject vArray = gl.createVertexArray(); int id = allocateVertexArrayId(vArray); ids.put(id); } ids.position(startPosition); } @Override public void glGetActiveUniformBlockiv (int program, int uniformBlockIndex, int pname, IntBuffer params) { if (pname == GL30.GL_UNIFORM_BLOCK_BINDING || pname == GL30.GL_UNIFORM_BLOCK_DATA_SIZE || pname == GL30.GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS) { params.put(gl.getActiveUniformBlockParameteri(programs.get(program), uniformBlockIndex, pname)); } else if (pname == GL30.GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) { Uint32Array array = gl.getActiveUniformBlockParameterv(programs.get(program), uniformBlockIndex, pname); for (int i = 0; i < array.length(); i++) { params.put(i, (int)array.get(i)); } } else if (pname == GL30.GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER || pname == GL30.GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER) { boolean result = gl.getActiveUniformBlockParameterb(programs.get(program), uniformBlockIndex, pname); params.put(result ? GL20.GL_TRUE : GL20.GL_FALSE); } else { throw new GdxRuntimeException(""Unsupported pname passed to glGetActiveUniformBlockiv""); } params.flip(); } @Override public String glGetActiveUniformBlockName (int program, int uniformBlockIndex) { return gl.getActiveUniformBlockName(programs.get(program), uniformBlockIndex); } @Override public void glGetActiveUniformBlockName (int program, int uniformBlockIndex, Buffer length, Buffer uniformBlockName) { throw new UnsupportedOperationException(""glGetActiveUniformBlockName with Buffer parameters not supported on WebGL2""); } @Override public void glGetActiveUniformsiv (int program, int uniformCount, IntBuffer uniformIndices, int pname, IntBuffer params) { if (pname == GL30.GL_UNIFORM_IS_ROW_MAJOR) { JsArrayBoolean arr = gl.getActiveUniformsb(programs.get(program), copy(uniformIndices).subarray(0, uniformCount), pname); for (int i = 0; i < uniformCount; i++) { params.put(i, arr.get(i) ? GL20.GL_TRUE : GL20.GL_FALSE); } } else { JsArrayInteger arr = gl.getActiveUniformsi(programs.get(program), copy(uniformIndices).subarray(0, uniformCount), pname); for (int i = 0; i < uniformCount; i++) { params.put(i, arr.get(i)); } } params.flip(); } @Override public void glGetBufferParameteri64v (int target, int pname, LongBuffer params) { throw new UnsupportedOperationException(""glGetBufferParameteri64v not supported on WebGL2""); } @Override public Buffer glGetBufferPointerv (int target, int pname) { throw new UnsupportedOperationException(""glGetBufferPointerv not supported on WebGL2""); } @Override public void glGetFloatv (int pname, FloatBuffer params) { // Override GwtGL20 method to check if it's a pname introduced with GL30. if (pname == GL30.GL_MAX_TEXTURE_LOD_BIAS) { params.put(0, gl.getParameterf(pname)); params.flip(); } else { super.glGetFloatv(pname, params); } } @Override public int glGetFragDataLocation (int program, String name) { return gl.getFragDataLocation(programs.get(program), name); } @Override public void glGetIntegerv (int pname, IntBuffer params) { // Override GwtGL20 method to check if it's a pname introduced with GL30. switch (pname) { case GL30.GL_DRAW_BUFFER0: case GL30.GL_DRAW_BUFFER1: case GL30.GL_DRAW_BUFFER2: case GL30.GL_DRAW_BUFFER3: case GL30.GL_DRAW_BUFFER4: case GL30.GL_DRAW_BUFFER5: case GL30.GL_DRAW_BUFFER6: case GL30.GL_DRAW_BUFFER7: case GL30.GL_DRAW_BUFFER8: case GL30.GL_DRAW_BUFFER9: case GL30.GL_DRAW_BUFFER10: case GL30.GL_FRAGMENT_SHADER_DERIVATIVE_HINT: case GL30.GL_MAX_3D_TEXTURE_SIZE: case GL30.GL_MAX_ARRAY_TEXTURE_LAYERS: case GL30.GL_MAX_COLOR_ATTACHMENTS: case GL30.GL_MAX_DRAW_BUFFERS: case GL30.GL_MAX_ELEMENTS_INDICES: case GL30.GL_MAX_ELEMENTS_VERTICES: case GL30.GL_MAX_FRAGMENT_INPUT_COMPONENTS: case GL30.GL_MAX_FRAGMENT_UNIFORM_BLOCKS: case GL30.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS: case GL30.GL_MAX_PROGRAM_TEXEL_OFFSET: case GL30.GL_MAX_SAMPLES: case GL30.GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS: case GL30.GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS: case GL30.GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS: case GL30.GL_MAX_UNIFORM_BUFFER_BINDINGS: case GL30.GL_MAX_VARYING_COMPONENTS: case GL30.GL_MAX_VERTEX_OUTPUT_COMPONENTS: case GL30.GL_MAX_VERTEX_UNIFORM_BLOCKS: case GL30.GL_MAX_VERTEX_UNIFORM_COMPONENTS: case GL30.GL_MIN_PROGRAM_TEXEL_OFFSET: case GL30.GL_PACK_ROW_LENGTH: case GL30.GL_PACK_SKIP_PIXELS: case GL30.GL_PACK_SKIP_ROWS: case GL30.GL_READ_BUFFER: case GL30.GL_UNPACK_IMAGE_HEIGHT: case GL30.GL_UNPACK_ROW_LENGTH: case GL30.GL_UNPACK_SKIP_IMAGES: case GL30.GL_UNPACK_SKIP_PIXELS: case GL30.GL_UNPACK_SKIP_ROWS: params.put(0, gl.getParameteri(pname)); params.flip(); return; case GL30.GL_DRAW_FRAMEBUFFER_BINDING: case GL30.GL_READ_FRAMEBUFFER_BINDING: WebGLFramebuffer fbo = gl.getParametero(pname); if (fbo == null) { params.put(0); } else { params.put(frameBuffers.getKey(fbo)); } params.flip(); return; case GL30.GL_TEXTURE_BINDING_2D_ARRAY: case GL30.GL_TEXTURE_BINDING_3D: WebGLTexture tex = gl.getParametero(pname); if (tex == null) { params.put(0); } else { params.put(textures.getKey(tex)); } params.flip(); return; case GL30.GL_VERTEX_ARRAY_BINDING: WebGLVertexArrayObject obj = gl.getParametero(pname); if (obj == null) { params.put(0); } else { params.put(vertexArrays.getKey(obj)); } params.flip(); return; default: // Assume it is a GL20 pname super.glGetIntegerv(pname, params); } } @Override public void glGetInteger64v (int pname, LongBuffer params) { switch (pname) { case GL30.GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS: case GL30.GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS: case GL30.GL_MAX_ELEMENT_INDEX: case GL30.GL_MAX_SERVER_WAIT_TIMEOUT: case GL30.GL_MAX_UNIFORM_BLOCK_SIZE: params.put(gl.getParameteri64(pname)); params.flip(); return; default: throw new UnsupportedOperationException(""Given glGetInteger64v enum not supported on WebGL2""); } } @Override public void glGetFramebufferAttachmentParameteriv (int target, int attachment, int pname, IntBuffer params) { switch (pname) { case GL30.GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: case GL30.GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: case GL30.GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: case GL30.GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: case GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: case GL30.GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: case GL30.GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE: case GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: case GL30.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: params.put(0, gl.getFramebufferAttachmentParameteri(target, attachment, pname)); params.flip(); break; default: // Assume it is a GL20 pname super.glGetFramebufferAttachmentParameteriv(target, attachment, pname, params); } } @Override public void glGetQueryiv (int target, int pname, IntBuffer params) { // Not 100% clear on this one. Returning the integer key for the query. // Similar to how GwtGL20 handles FBO in glGetIntegerv WebGLQuery query = gl.getQuery(target, pname); if (query == null) { params.put(0); } else { params.put(queries.getKey(query)); } params.flip(); } @Override public void glGetQueryObjectuiv (int id, int pname, IntBuffer params) { // In WebGL2 getQueryObject was renamed to getQueryParameter if (pname == GL30.GL_QUERY_RESULT) { params.put(gl.getQueryParameteri(queries.get(id), pname)); } else if (pname == GL30.GL_QUERY_RESULT_AVAILABLE) { boolean result = gl.getQueryParameterb(queries.get(id), pname); params.put(result ? GL20.GL_TRUE : GL20.GL_FALSE); } else { throw new GdxRuntimeException(""Unsupported pname passed to glGetQueryObjectuiv""); } params.flip(); } @Override public void glGetSamplerParameterfv (int sampler, int pname, FloatBuffer params) { params.put(gl.getSamplerParameterf(samplers.get(sampler), pname)); params.flip(); } @Override public void glGetSamplerParameteriv (int sampler, int pname, IntBuffer params) { params.put(gl.getSamplerParameteri(samplers.get(sampler), pname)); params.flip(); } @Override public String glGetStringi (int name, int index) { throw new UnsupportedOperationException(""glGetStringi not supported on WebGL2""); } @Override public int glGetUniformBlockIndex (int program, String uniformBlockName) { return gl.getUniformBlockIndex(programs.get(program), uniformBlockName); } @Override public void glGetUniformIndices (int program, String[] uniformNames, IntBuffer uniformIndices) { JsArrayInteger array = gl.getUniformIndices(programs.get(program), uniformNames); for (int i = 0; i < array.length(); i++) { uniformIndices.put(i, array.get(i)); } uniformIndices.flip(); } @Override public void glGetUniformuiv (int program, int location, IntBuffer params) { // fv and iv also not implemented in GwtGL20 throw new UnsupportedOperationException(""glGetUniformuiv not implemented on WebGL2""); } @Override public void glGetVertexAttribIiv (int index, int pname, IntBuffer params) { // fv and iv also not implemented in GwtGL20 throw new UnsupportedOperationException(""glGetVertexAttribIiv not implemented on WebGL2""); } @Override public void glGetVertexAttribIuiv (int index, int pname, IntBuffer params) { // fv and iv also not implemented in GwtGL20 throw new UnsupportedOperationException(""glGetVertexAttribIuiv not implemented on WebGL2""); } @Override public void glInvalidateFramebuffer (int target, int numAttachments, IntBuffer attachments) { int startPosition = attachments.position(); gl.invalidateFramebuffer(target, copy((IntBuffer)attachments).subarray(0, numAttachments)); attachments.position(startPosition); } @Override public void glInvalidateSubFramebuffer (int target, int numAttachments, IntBuffer attachments, int x, int y, int width, int height) { int startPosition = attachments.position(); gl.invalidateSubFramebuffer(target, copy((IntBuffer)attachments).subarray(0, numAttachments), x, y, width, height); attachments.position(startPosition); } @Override public boolean glIsQuery (int id) { return gl.isQuery(queries.get(id)); } @Override public boolean glIsSampler (int id) { return gl.isSampler(samplers.get(id)); } @Override public boolean glIsTransformFeedback (int id) { return gl.isTransformFeedback(feedbacks.get(id)); } @Override public boolean glIsVertexArray (int id) { return gl.isVertexArray(vertexArrays.get(id)); } @Override public Buffer glMapBufferRange (int target, int offset, int length, int access) { throw new UnsupportedOperationException(""glMapBufferRange not supported on WebGL2""); } @Override public void glPauseTransformFeedback () { gl.pauseTransformFeedback(); } @Override public void glProgramParameteri (int program, int pname, int value) { // Per WebGL2 spec: Accessing binary representations of compiled shader programs is not supported in the WebGL 2.0 API. // This includes OpenGL ES 3.0 GetProgramBinary, ProgramBinary, and ProgramParameteri entry points throw new UnsupportedOperationException(""glProgramParameteri not supported on WebGL2""); } @Override public void glReadBuffer (int mode) { gl.readBuffer(mode); } @Override public void glRenderbufferStorageMultisample (int target, int samples, int internalformat, int width, int height) { gl.renderbufferStorageMultisample(target, samples, internalformat, width, height); } @Override public void glResumeTransformFeedback () { gl.resumeTransformFeedback(); } @Override public void glSamplerParameterf (int sampler, int pname, float param) { gl.samplerParameterf(samplers.get(sampler), pname, param); } @Override public void glSamplerParameterfv (int sampler, int pname, FloatBuffer param) { gl.samplerParameterf(samplers.get(sampler), pname, param.get()); } @Override public void glSamplerParameteri (int sampler, int pname, int param) { gl.samplerParameteri(samplers.get(sampler), pname, param); } @Override public void glSamplerParameteriv (int sampler, int pname, IntBuffer param) { gl.samplerParameterf(samplers.get(sampler), pname, param.get()); } @Override public void glTexImage3D (int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, Buffer pixels) { // Taken from glTexImage2D if (pixels == null) { gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, (ArrayBufferView)null); return; } if (pixels.limit() > 1) { HasArrayBufferView arrayHolder = (HasArrayBufferView)pixels; ArrayBufferView webGLArray = arrayHolder.getTypedArray(); ArrayBufferView buffer; if (pixels instanceof FloatBuffer) { buffer = webGLArray; } else { int length = pixels.remaining(); if (!(pixels instanceof ByteBuffer)) { // It seems for ByteBuffer we don't need this byte conversion length *= 4; } int byteOffset = webGLArray.byteOffset() + pixels.position() * 4; buffer = Uint8ArrayNative.create(webGLArray.buffer(), byteOffset, length); } gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, buffer); } else { Pixmap pixmap = Pixmap.pixmaps.get(((IntBuffer)pixels).get(0)); // Prefer to use the HTMLImageElement when possible, since reading from the CanvasElement can be lossy. if (pixmap.canUseImageElement()) { gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixmap.getImageElement()); } else if (pixmap.canUseVideoElement()) { gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixmap.getVideoElement()); } else { gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixmap.getCanvasElement()); } } } @Override public void glTexImage3D (int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, int offset) { gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, offset); } @Override public void glTexSubImage2D (int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, int offset) { gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, offset); } @Override public void glTexSubImage3D (int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, Buffer pixels) { // Taken from glTexSubImage2D if (pixels.limit() > 1) { HasArrayBufferView arrayHolder = (HasArrayBufferView)pixels; ArrayBufferView webGLArray = arrayHolder.getTypedArray(); ArrayBufferView buffer; if (pixels instanceof FloatBuffer) { buffer = webGLArray; } else { int length = pixels.remaining(); if (!(pixels instanceof ByteBuffer)) { // It seems for ByteBuffer we don't need this byte conversion length *= 4; } int byteOffset = webGLArray.byteOffset() + pixels.position() * 4; buffer = Uint8ArrayNative.create(webGLArray.buffer(), byteOffset, length); } gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, buffer); } else { Pixmap pixmap = Pixmap.pixmaps.get(((IntBuffer)pixels).get(0)); gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixmap.getCanvasElement()); } } @Override public void glTexSubImage3D (int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, int offset) { gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, offset); } @Override public void glTransformFeedbackVaryings (int program, String[] [MASK] , int bufferMode) { gl.transformFeedbackVaryings(programs.get(program), [MASK] , bufferMode); } @Override public void glUniform1uiv (int location, int count, IntBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniform1uiv(loc, copyUnsigned(value), 0, count); } @Override public void glUniform3uiv (int location, int count, IntBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniform3uiv(loc, copyUnsigned(value), 0, count); } @Override public void glUniform4uiv (int location, int count, IntBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniform4uiv(loc, copyUnsigned(value), 0, count); } @Override public void glUniformBlockBinding (int program, int uniformBlockIndex, int uniformBlockBinding) { gl.uniformBlockBinding(programs.get(program), uniformBlockIndex, uniformBlockBinding); } @Override public void glUniformMatrix2x3fv (int location, int count, boolean transpose, FloatBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniformMatrix2x3fv(loc, transpose, copy(value)); } @Override public void glUniformMatrix2x4fv (int location, int count, boolean transpose, FloatBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniformMatrix2x4fv(loc, transpose, copy(value), 0, count); } @Override public void glUniformMatrix3x2fv (int location, int count, boolean transpose, FloatBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniformMatrix3x2fv(loc, transpose, copy(value), 0, count); } @Override public void glUniformMatrix3x4fv (int location, int count, boolean transpose, FloatBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniformMatrix3x4fv(loc, transpose, copy(value), 0, count); } @Override public void glUniformMatrix4x2fv (int location, int count, boolean transpose, FloatBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniformMatrix4x2fv(loc, transpose, copy(value), 0, count); } @Override public void glUniformMatrix4x3fv (int location, int count, boolean transpose, FloatBuffer value) { WebGLUniformLocation loc = getUniformLocation(location); gl.uniformMatrix4x3fv(loc, transpose, copy(value), 0, count); } @Override public boolean glUnmapBuffer (int target) { throw new UnsupportedOperationException(""glUnmapBuffer not supported on WebGL2""); } @Override public void glVertexAttribDivisor (int index, int divisor) { gl.vertexAttribDivisor(index, divisor); } @Override public void glVertexAttribI4i (int index, int x, int y, int z, int w) { gl.vertexAttribI4i(index, x, y, z, w); } @Override public void glVertexAttribI4ui (int index, int x, int y, int z, int w) { gl.vertexAttribI4ui(index, x, y, z, w); } @Override public void glVertexAttribIPointer (int index, int size, int type, int stride, int offset) { gl.vertexAttribIPointer(index, size, type, stride, offset); } } ","varyings " "/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the * ""License""); you may not use this file except in compliance with the License. You may obtain a * copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an ""AS IS"" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package io.netty.handler.codec.http2; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalChannel; import io.netty.channel.local.LocalServerChannel; import io.netty.handler.codec.http2.Http2TestUtil.FrameCountDown; import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable; import io.netty.util.AsciiString; import io.netty.util.IllegalReferenceCountException; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.ByteArrayOutputStream; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static io.netty.buffer.Unpooled.EMPTY_BUFFER; import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT; import static io.netty.handler.codec.http2.Http2Error.NO_ERROR; import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; import static io.netty.handler.codec.http2.Http2TestUtil.randomString; import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel; import static java.lang.Integer.MAX_VALUE; import static java.util.concurrent.TimeUnit.SECONDS; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyShort; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * Tests the full HTTP/2 framing stack including the connection and preface handlers. */ public class Http2ConnectionRoundtripTest { private static final long DEFAULT_AWAIT_TIMEOUT_SECONDS = 15; @Mock private Http2FrameListener clientListener; @Mock private Http2FrameListener serverListener; private Http2ConnectionHandler http2Client; private Http2ConnectionHandler http2Server; private ServerBootstrap sb; private Bootstrap cb; private Channel serverChannel; private volatile Channel serverConnectedChannel; private Channel clientChannel; private FrameCountDown serverFrameCountDown; private CountDownLatch requestLatch; private CountDownLatch serverSettingsAckLatch; private CountDownLatch dataLatch; private CountDownLatch trailersLatch; private CountDownLatch goAwayLatch; @BeforeEach public void setup() throws Exception { MockitoAnnotations.initMocks(this); mockFlowControl(clientListener); mockFlowControl(serverListener); } @AfterEach public void teardown() throws Exception { if (clientChannel != null) { clientChannel.close().syncUninterruptibly(); clientChannel = null; } if (serverChannel != null) { serverChannel.close().syncUninterruptibly(); serverChannel = null; } final Channel serverConnectedChannel = this.serverConnectedChannel; if (serverConnectedChannel != null) { serverConnectedChannel.close().syncUninterruptibly(); this.serverConnectedChannel = null; } Future serverGroup = sb.config().group().shutdownGracefully(0, 5, SECONDS); Future [MASK] = sb.config().childGroup().shutdownGracefully(0, 5, SECONDS); Future clientGroup = cb.config().group().shutdownGracefully(0, 5, SECONDS); serverGroup.syncUninterruptibly(); [MASK] .syncUninterruptibly(); clientGroup.syncUninterruptibly(); } @Test public void inflightFrameAfterStreamResetShouldNotMakeConnectionUnusable() throws Exception { final CountDownLatch latch = new CountDownLatch(1); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { ChannelHandlerContext ctx = invocationOnMock.getArgument(0); http2Server.encoder().writeHeaders(ctx, (Integer) invocationOnMock.getArgument(1), (Http2Headers) invocationOnMock.getArgument(2), 0, false, ctx.newPromise()); http2Server.flush(ctx); return null; } }).when(serverListener).onHeadersRead(any(ChannelHandlerContext.class), anyInt(), any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { latch.countDown(); return null; } }).when(clientListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5), any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()); bootstrapEnv(1, 1, 2, 1); // Create a single stream by sending a HEADERS frame to the server. final short weight = 16; final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, weight, false, 0, false, newPromise()); http2Client.flush(ctx()); http2Client.encoder().writeRstStream(ctx(), 3, Http2Error.INTERNAL_ERROR.code(), newPromise()); http2Client.flush(ctx()); } }); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, weight, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); assertTrue(latch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); } @Test public void headersWithEndStreamShouldNotSendError() throws Exception { bootstrapEnv(1, 1, 2, 1); // Create a single stream by sending a HEADERS frame to the server. final short weight = 16; final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, weight, false, 0, true, newPromise()); http2Client.flush(ctx()); } }); assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(headers), eq(0), eq(weight), eq(false), eq(0), eq(true)); // Wait for some time to see if a go_away or reset frame will be received. Thread.sleep(1000); // Verify that no errors have been received. verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(serverListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); // The server will not respond, and so don't wait for graceful shutdown setClientGracefulShutdownTime(0); } @Test public void encodeViolatesMaxHeaderListSizeCanStillUseConnection() throws Exception { final CountDownLatch serverSettingsAckLatch1 = new CountDownLatch(2); final CountDownLatch serverSettingsAckLatch2 = new CountDownLatch(3); final CountDownLatch clientSettingsLatch1 = new CountDownLatch(3); final CountDownLatch serverRevHeadersLatch = new CountDownLatch(1); final CountDownLatch clientHeadersLatch = new CountDownLatch(1); final CountDownLatch clientDataWrite = new CountDownLatch(1); final AtomicReference clientHeadersWriteException = new AtomicReference(); final AtomicReference clientHeadersWriteException2 = new AtomicReference(); final AtomicReference clientDataWriteException = new AtomicReference(); final Http2Headers headers = dummyHeaders(); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { serverSettingsAckLatch1.countDown(); serverSettingsAckLatch2.countDown(); return null; } }).when(serverListener).onSettingsAckRead(any(ChannelHandlerContext.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { clientSettingsLatch1.countDown(); return null; } }).when(clientListener).onSettingsRead(any(ChannelHandlerContext.class), any(Http2Settings.class)); // Manually add a listener for when we receive the expected headers on the server. doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { serverRevHeadersLatch.countDown(); return null; } }).when(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5), eq(headers), anyInt(), anyShort(), anyBoolean(), eq(0), eq(true)); bootstrapEnv(1, 2, 2, 0, 0); // Set the maxHeaderListSize to 100 so we may be able to write some headers, but not all. We want to verify // that we don't corrupt state if some can be written but not all. runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Server.encoder().writeSettings(serverCtx(), new Http2Settings().copyFrom(http2Server.decoder().localSettings()) .maxHeaderListSize(100), serverNewPromise()); http2Server.flush(serverCtx()); } }); assertTrue(serverSettingsAckLatch1.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, false, newPromise()) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { clientHeadersWriteException.set(future.cause()); } }); // It is expected that this write should fail locally and the remote peer will never see this. http2Client.encoder().writeData(ctx(), 3, Unpooled.buffer(), 0, true, newPromise()) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { clientDataWriteException.set(future.cause()); clientDataWrite.countDown(); } }); http2Client.flush(ctx()); } }); assertTrue(clientDataWrite.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertNotNull(clientHeadersWriteException.get(), ""Header encode should have exceeded maxHeaderListSize!""); assertNotNull(clientDataWriteException.get(), ""Data on closed stream should fail!""); // Set the maxHeaderListSize to the max value so we can send the headers. runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Server.encoder().writeSettings(serverCtx(), new Http2Settings().copyFrom(http2Server.decoder().localSettings()) .maxHeaderListSize(Http2CodecUtil.MAX_HEADER_LIST_SIZE), serverNewPromise()); http2Server.flush(serverCtx()); } }); assertTrue(clientSettingsLatch1.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(serverSettingsAckLatch2.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, true, newPromise()).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { clientHeadersWriteException2.set(future.cause()); clientHeadersLatch.countDown(); } }); http2Client.flush(ctx()); } }); assertTrue(clientHeadersLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertNull(clientHeadersWriteException2.get(), ""Client write of headers should succeed with increased header list size!""); assertTrue(serverRevHeadersLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()); // Verify that no errors have been received. verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(serverListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); verify(clientListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(clientListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); } @Test public void testSettingsAckIsSentBeforeUsingFlowControl() throws Exception { final CountDownLatch serverSettingsAckLatch1 = new CountDownLatch(1); final CountDownLatch serverSettingsAckLatch2 = new CountDownLatch(2); final CountDownLatch serverDataLatch = new CountDownLatch(1); final CountDownLatch clientWriteDataLatch = new CountDownLatch(1); final byte[] data = new byte[] {1, 2, 3, 4, 5}; final ByteArrayOutputStream out = new ByteArrayOutputStream(data.length); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { serverSettingsAckLatch1.countDown(); serverSettingsAckLatch2.countDown(); return null; } }).when(serverListener).onSettingsAckRead(any(ChannelHandlerContext.class)); doAnswer(new Answer() { @Override public Integer answer(InvocationOnMock in) throws Throwable { ByteBuf buf = (ByteBuf) in.getArguments()[2]; int padding = (Integer) in.getArguments()[3]; int processedBytes = buf.readableBytes() + padding; buf.readBytes(out, buf.readableBytes()); serverDataLatch.countDown(); return processedBytes; } }).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), any(ByteBuf.class), eq(0), anyBoolean()); bootstrapEnv(1, 1, 2, 1); final Http2Headers headers = dummyHeaders(); // The server initially reduces the connection flow control window to 0. runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Server.encoder().writeSettings(serverCtx(), new Http2Settings().copyFrom(http2Server.decoder().localSettings()) .initialWindowSize(0), serverNewPromise()); http2Server.flush(serverCtx()); } }); assertTrue(serverSettingsAckLatch1.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // The client should now attempt to send data, but the window size is 0 so it will be queued in the flow // controller. runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.encoder().writeData(ctx(), 3, Unpooled.wrappedBuffer(data), 0, true, newPromise()); http2Client.flush(ctx()); clientWriteDataLatch.countDown(); } }); assertTrue(clientWriteDataLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // Now the server opens up the connection window to allow the client to send the pending data. runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Server.encoder().writeSettings(serverCtx(), new Http2Settings().copyFrom(http2Server.decoder().localSettings()) .initialWindowSize(data.length), serverNewPromise()); http2Server.flush(serverCtx()); } }); assertTrue(serverSettingsAckLatch2.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(serverDataLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertArrayEquals(data, out.toByteArray()); // Verify that no errors have been received. verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(serverListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); verify(clientListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(clientListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); } @Test public void priorityUsingHigherValuedStreamIdDoesNotPreventUsingLowerStreamId() throws Exception { bootstrapEnv(1, 1, 3, 0); final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writePriority(ctx(), 5, 3, (short) 14, false, newPromise()); http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(serverListener).onPriorityRead(any(ChannelHandlerContext.class), eq(5), eq(3), eq((short) 14), eq(false)); verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false)); // Verify that no errors have been received. verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(serverListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); verify(clientListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(clientListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); } @Test public void headersUsingHigherValuedStreamIdPreventsUsingLowerStreamId() throws Exception { bootstrapEnv(1, 1, 2, 0); final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.encoder().frameWriter().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(5), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false)); verify(serverListener, never()).onHeadersRead(any(ChannelHandlerContext.class), eq(3), any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()); // Client should receive a RST_STREAM for stream 3, but there is not Http2Stream object so the listener is never // notified. verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(serverListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); verify(clientListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(clientListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); } @Test public void headersWriteForPeerStreamWhichWasResetShouldNotGoAway() throws Exception { final CountDownLatch serverGotRstLatch = new CountDownLatch(1); final CountDownLatch serverWriteHeadersLatch = new CountDownLatch(1); final AtomicReference serverWriteHeadersCauseRef = new AtomicReference(); final int streamId = 3; doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { if (streamId == (Integer) invocationOnMock.getArgument(1)) { serverGotRstLatch.countDown(); } return null; } }).when(serverListener).onRstStreamRead(any(ChannelHandlerContext.class), eq(streamId), anyLong()); bootstrapEnv(1, 1, 1, 0); final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), streamId, headers, CONNECTION_STREAM_ID, DEFAULT_PRIORITY_WEIGHT, false, 0, false, newPromise()); http2Client.encoder().writeRstStream(ctx(), streamId, Http2Error.CANCEL.code(), newPromise()); http2Client.flush(ctx()); } }); assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(serverGotRstLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(streamId), eq(headers), anyInt(), anyShort(), anyBoolean(), anyInt(), eq(false)); // Now have the server attempt to send a headers frame simulating some asynchronous work. runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Server.encoder().writeHeaders(serverCtx(), streamId, headers, 0, true, serverNewPromise()) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { serverWriteHeadersCauseRef.set(future.cause()); serverWriteHeadersLatch.countDown(); } }); http2Server.flush(serverCtx()); } }); assertTrue(serverWriteHeadersLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); Throwable serverWriteHeadersCause = serverWriteHeadersCauseRef.get(); assertNotNull(serverWriteHeadersCause); assertThat(serverWriteHeadersCauseRef.get(), not(instanceOf(Http2Exception.class))); // Server should receive a RST_STREAM for stream 3. verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(clientListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); verify(clientListener, never()).onRstStreamRead(any(ChannelHandlerContext.class), anyInt(), anyLong()); } @Test public void http2ExceptionInPipelineShouldCloseConnection() throws Exception { bootstrapEnv(1, 1, 2, 1); // Create a latch to track when the close occurs. final CountDownLatch closeLatch = new CountDownLatch(1); clientChannel.closeFuture().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { closeLatch.countDown(); } }); // Create a single stream by sending a HEADERS frame to the server. final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); // Wait for the server to create the stream. assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // Add a handler that will immediately throw an exception. clientChannel.pipeline().addFirst(new ChannelHandlerAdapter() { @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { throw Http2Exception.connectionError(PROTOCOL_ERROR, ""Fake Exception""); } }); // Wait for the close to occur. assertTrue(closeLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertFalse(clientChannel.isOpen()); } @Test public void listenerExceptionShouldCloseConnection() throws Exception { final Http2Headers headers = dummyHeaders(); doThrow(new RuntimeException(""Fake Exception"")).when(serverListener).onHeadersRead( any(ChannelHandlerContext.class), eq(3), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false)); bootstrapEnv(1, 0, 1, 1); // Create a latch to track when the close occurs. final CountDownLatch closeLatch = new CountDownLatch(1); clientChannel.closeFuture().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { closeLatch.countDown(); } }); // Create a single stream by sending a HEADERS frame to the server. runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); // Wait for the server to create the stream. assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // Wait for the close to occur. assertTrue(closeLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertFalse(clientChannel.isOpen()); } private enum WriteEmptyBufferMode { SINGLE_END_OF_STREAM, SECOND_END_OF_STREAM, SINGLE_WITH_TRAILERS, SECOND_WITH_TRAILERS } @Test public void writeOfEmptyReleasedBufferSingleBufferQueuedInFlowControllerShouldFail() throws Exception { writeOfEmptyReleasedBufferQueuedInFlowControllerShouldFail(WriteEmptyBufferMode.SINGLE_END_OF_STREAM); } @Test public void writeOfEmptyReleasedBufferSingleBufferTrailersQueuedInFlowControllerShouldFail() throws Exception { writeOfEmptyReleasedBufferQueuedInFlowControllerShouldFail(WriteEmptyBufferMode.SINGLE_WITH_TRAILERS); } @Test public void writeOfEmptyReleasedBufferMultipleBuffersQueuedInFlowControllerShouldFail() throws Exception { writeOfEmptyReleasedBufferQueuedInFlowControllerShouldFail(WriteEmptyBufferMode.SECOND_END_OF_STREAM); } @Test public void writeOfEmptyReleasedBufferMultipleBuffersTrailersQueuedInFlowControllerShouldFail() throws Exception { writeOfEmptyReleasedBufferQueuedInFlowControllerShouldFail(WriteEmptyBufferMode.SECOND_WITH_TRAILERS); } private void writeOfEmptyReleasedBufferQueuedInFlowControllerShouldFail(final WriteEmptyBufferMode mode) throws Exception { bootstrapEnv(1, 1, 2, 1); final ChannelPromise emptyDataPromise = newPromise(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, false, newPromise()); ByteBuf emptyBuf = Unpooled.buffer(); emptyBuf.release(); switch (mode) { case SINGLE_END_OF_STREAM: http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, true, emptyDataPromise); break; case SECOND_END_OF_STREAM: http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false, emptyDataPromise); http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, true, newPromise()); break; case SINGLE_WITH_TRAILERS: http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false, emptyDataPromise); http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, true, newPromise()); break; case SECOND_WITH_TRAILERS: http2Client.encoder().writeData(ctx(), 3, emptyBuf, 0, false, emptyDataPromise); http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, false, newPromise()); http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, true, newPromise()); break; default: throw new Error(); } http2Client.flush(ctx()); } }); ExecutionException e = assertThrows(ExecutionException.class, new Executable() { @Override public void execute() throws Throwable { emptyDataPromise.get(); } }); assertThat(e.getCause(), is(instanceOf(IllegalReferenceCountException.class))); } @Test public void writeFailureFlowControllerRemoveFrame() throws Exception { bootstrapEnv(1, 1, 3, 1); final ChannelPromise dataPromise = newPromise(); final ChannelPromise assertPromise = newPromise(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, EmptyHttp2Headers.INSTANCE, 0, (short) 16, false, 0, false, newPromise()); clientChannel.pipeline().addFirst(new ChannelOutboundHandlerAdapter() { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { ReferenceCountUtil.release(msg); // Ensure we update the window size so we will try to write the rest of the frame while // processing the flush. http2Client.encoder().flowController().initialWindowSize(8); promise.setFailure(new IllegalStateException()); } }); http2Client.encoder().flowController().initialWindowSize(4); http2Client.encoder().writeData(ctx(), 3, randomBytes(8), 0, false, dataPromise); assertTrue(http2Client.encoder().flowController() .hasFlowControlled(http2Client.connection().stream(3))); http2Client.flush(ctx()); try { // The Frame should have been removed after the write failed. assertFalse(http2Client.encoder().flowController() .hasFlowControlled(http2Client.connection().stream(3))); assertPromise.setSuccess(); } catch (Throwable error) { assertPromise.setFailure(error); } } }); ExecutionException e = assertThrows(ExecutionException.class, new Executable() { @Override public void execute() throws Throwable { dataPromise.get(); } }); assertThat(e.getCause(), is(instanceOf(IllegalStateException.class))); assertPromise.sync(); } @Test public void nonHttp2ExceptionInPipelineShouldNotCloseConnection() throws Exception { bootstrapEnv(1, 1, 2, 1); // Create a latch to track when the close occurs. final CountDownLatch closeLatch = new CountDownLatch(1); clientChannel.closeFuture().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { closeLatch.countDown(); } }); // Create a single stream by sending a HEADERS frame to the server. final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); // Wait for the server to create the stream. assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // Add a handler that will immediately throw an exception. clientChannel.pipeline().addFirst(new ChannelHandlerAdapter() { @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { throw new RuntimeException(""Fake Exception""); } }); // The close should NOT occur. assertFalse(closeLatch.await(2, SECONDS)); assertTrue(clientChannel.isOpen()); // Set the timeout very low because we know graceful shutdown won't complete setClientGracefulShutdownTime(0); } @Test public void noMoreStreamIdsShouldSendGoAway() throws Exception { bootstrapEnv(1, 1, 4, 1, 1); // Don't wait for the server to close streams setClientGracefulShutdownTime(0); // Create a single stream by sending a HEADERS frame to the server. final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, true, newPromise()); http2Client.flush(ctx()); } }); assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), MAX_VALUE + 1, headers, 0, (short) 16, false, 0, true, newPromise()); http2Client.flush(ctx()); } }); assertTrue(goAwayLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(serverListener).onGoAwayRead(any(ChannelHandlerContext.class), eq(0), eq(PROTOCOL_ERROR.code()), any(ByteBuf.class)); } @Test public void createStreamAfterReceiveGoAwayShouldNotSendGoAway() throws Exception { final CountDownLatch clientGoAwayLatch = new CountDownLatch(1); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { clientGoAwayLatch.countDown(); return null; } }).when(clientListener).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); bootstrapEnv(1, 1, 2, 1, 1); // We want both sides to do graceful shutdown during the test. setClientGracefulShutdownTime(10000); setServerGracefulShutdownTime(10000); // Create a single stream by sending a HEADERS frame to the server. final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // Server has received the headers, so the stream is open assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); runInChannel(serverChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Server.encoder().writeGoAway(serverCtx(), 3, NO_ERROR.code(), EMPTY_BUFFER, serverNewPromise()); http2Server.flush(serverCtx()); } }); // wait for the client to receive the GO_AWAY. assertTrue(clientGoAwayLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(clientListener).onGoAwayRead(any(ChannelHandlerContext.class), eq(3), eq(NO_ERROR.code()), any(ByteBuf.class)); final AtomicReference clientWriteAfterGoAwayFutureRef = new AtomicReference(); final CountDownLatch clientWriteAfterGoAwayLatch = new CountDownLatch(1); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { ChannelFuture f = http2Client.encoder().writeHeaders(ctx(), 5, headers, 0, (short) 16, false, 0, true, newPromise()); clientWriteAfterGoAwayFutureRef.set(f); http2Client.flush(ctx()); f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { clientWriteAfterGoAwayLatch.countDown(); } }); } }); // Wait for the client's write operation to complete. assertTrue(clientWriteAfterGoAwayLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); ChannelFuture clientWriteAfterGoAwayFuture = clientWriteAfterGoAwayFutureRef.get(); assertNotNull(clientWriteAfterGoAwayFuture); Throwable clientCause = clientWriteAfterGoAwayFuture.cause(); assertThat(clientCause, is(instanceOf(Http2Exception.StreamException.class))); assertEquals(Http2Error.REFUSED_STREAM.code(), ((Http2Exception.StreamException) clientCause).error().code()); // Wait for the server to receive a GO_AWAY, but this is expected to timeout! assertFalse(goAwayLatch.await(1, SECONDS)); verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); // Shutdown shouldn't wait for the server to close streams setClientGracefulShutdownTime(0); setServerGracefulShutdownTime(0); } @Test public void listenerIsNotifiedOfGoawayBeforeStreamsAreRemovedFromTheConnection() throws Exception { final AtomicReference clientStream3State = new AtomicReference(); final CountDownLatch clientGoAwayLatch = new CountDownLatch(1); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocationOnMock) throws Throwable { clientStream3State.set(http2Client.connection().stream(3).state()); clientGoAwayLatch.countDown(); return null; } }).when(clientListener).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); bootstrapEnv(1, 1, 3, 1, 1); // We want both sides to do graceful shutdown during the test. setClientGracefulShutdownTime(10000); setServerGracefulShutdownTime(10000); // Create a single stream by sending a HEADERS frame to the server. final Http2Headers headers = dummyHeaders(); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 1, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.flush(ctx()); } }); assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // Server has received the headers, so the stream is open assertTrue(requestLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); runInChannel(serverChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Server.encoder().writeGoAway(serverCtx(), 1, NO_ERROR.code(), EMPTY_BUFFER, serverNewPromise()); http2Server.flush(serverCtx()); } }); // wait for the client to receive the GO_AWAY. assertTrue(clientGoAwayLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); verify(clientListener).onGoAwayRead(any(ChannelHandlerContext.class), eq(1), eq(NO_ERROR.code()), any(ByteBuf.class)); assertEquals(Http2Stream.State.OPEN, clientStream3State.get()); // Make sure that stream 3 has been closed which is true if it's gone. final CountDownLatch probeStreamCount = new CountDownLatch(1); final AtomicBoolean stream3Exists = new AtomicBoolean(); final AtomicInteger streamCount = new AtomicInteger(); runInChannel(this.clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { stream3Exists.set(http2Client.connection().stream(3) != null); streamCount.set(http2Client.connection().numActiveStreams()); probeStreamCount.countDown(); } }); // The stream should be closed right after assertTrue(probeStreamCount.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertEquals(1, streamCount.get()); assertFalse(stream3Exists.get()); // Wait for the server to receive a GO_AWAY, but this is expected to timeout! assertFalse(goAwayLatch.await(1, SECONDS)); verify(serverListener, never()).onGoAwayRead(any(ChannelHandlerContext.class), anyInt(), anyLong(), any(ByteBuf.class)); // Shutdown shouldn't wait for the server to close streams setClientGracefulShutdownTime(0); setServerGracefulShutdownTime(0); } @Test public void flowControlProperlyChunksLargeMessage() throws Exception { final Http2Headers headers = dummyHeaders(); // Create a large message to send. final int length = 10485760; // 10MB // Create a buffer filled with random bytes. final ByteBuf data = randomBytes(length); final ByteArrayOutputStream out = new ByteArrayOutputStream(length); doAnswer(new Answer() { @Override public Integer answer(InvocationOnMock in) throws Throwable { ByteBuf buf = (ByteBuf) in.getArguments()[2]; int padding = (Integer) in.getArguments()[3]; int processedBytes = buf.readableBytes() + padding; buf.readBytes(out, buf.readableBytes()); return processedBytes; } }).when(serverListener).onDataRead(any(ChannelHandlerContext.class), eq(3), any(ByteBuf.class), eq(0), anyBoolean()); try { // Initialize the data latch based on the number of bytes expected. bootstrapEnv(length, 1, 3, 1); // Create the stream and send all of the data at once. runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.encoder().writeData(ctx(), 3, data.retainedDuplicate(), 0, false, newPromise()); // Write trailers. http2Client.encoder().writeHeaders(ctx(), 3, headers, 0, (short) 16, false, 0, true, newPromise()); http2Client.flush(ctx()); } }); // Wait for the trailers to be received. assertTrue(serverSettingsAckLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); assertTrue(trailersLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); // Verify that headers and trailers were received. verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false)); verify(serverListener).onHeadersRead(any(ChannelHandlerContext.class), eq(3), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(true)); // Verify we received all the bytes. assertEquals(0, dataLatch.getCount()); out.flush(); byte[] received = out.toByteArray(); assertArrayEquals(data.array(), received); } finally { // Don't wait for server to close streams setClientGracefulShutdownTime(0); data.release(); out.close(); } } @Test public void stressTest() throws Exception { final Http2Headers headers = dummyHeaders(); int length = 10; final ByteBuf data = randomBytes(length); final String dataAsHex = ByteBufUtil.hexDump(data); final long pingData = 8; final int numStreams = 2000; // Collect all the ping buffers as we receive them at the server. final long[] receivedPings = new long[numStreams]; doAnswer(new Answer() { int nextIndex; @Override public Void answer(InvocationOnMock in) throws Throwable { receivedPings[nextIndex++] = (Long) in.getArguments()[1]; return null; } }).when(serverListener).onPingRead(any(ChannelHandlerContext.class), any(Long.class)); // Collect all the data buffers as we receive them at the server. final StringBuilder[] receivedData = new StringBuilder[numStreams]; doAnswer(new Answer() { @Override public Integer answer(InvocationOnMock in) throws Throwable { int streamId = (Integer) in.getArguments()[1]; ByteBuf buf = (ByteBuf) in.getArguments()[2]; int padding = (Integer) in.getArguments()[3]; int processedBytes = buf.readableBytes() + padding; int streamIndex = (streamId - 3) / 2; StringBuilder builder = receivedData[streamIndex]; if (builder == null) { builder = new StringBuilder(dataAsHex.length()); receivedData[streamIndex] = builder; } builder.append(ByteBufUtil.hexDump(buf)); return processedBytes; } }).when(serverListener).onDataRead(any(ChannelHandlerContext.class), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()); try { bootstrapEnv(numStreams * length, 1, numStreams * 4 + 1 , numStreams); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() throws Http2Exception { int upperLimit = 3 + 2 * numStreams; for (int streamId = 3; streamId < upperLimit; streamId += 2) { // Send a bunch of data on each stream. http2Client.encoder().writeHeaders(ctx(), streamId, headers, 0, (short) 16, false, 0, false, newPromise()); http2Client.encoder().writePing(ctx(), false, pingData, newPromise()); http2Client.encoder().writeData(ctx(), streamId, data.retainedSlice(), 0, false, newPromise()); // Write trailers. http2Client.encoder().writeHeaders(ctx(), streamId, headers, 0, (short) 16, false, 0, true, newPromise()); http2Client.flush(ctx()); } } }); // Wait for all frames to be received. assertTrue(serverSettingsAckLatch.await(60, SECONDS)); assertTrue(trailersLatch.await(60, SECONDS)); verify(serverListener, times(numStreams)).onHeadersRead(any(ChannelHandlerContext.class), anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(false)); verify(serverListener, times(numStreams)).onHeadersRead(any(ChannelHandlerContext.class), anyInt(), eq(headers), eq(0), eq((short) 16), eq(false), eq(0), eq(true)); verify(serverListener, times(numStreams)).onPingRead(any(ChannelHandlerContext.class), any(long.class)); verify(serverListener, never()).onDataRead(any(ChannelHandlerContext.class), anyInt(), any(ByteBuf.class), eq(0), eq(true)); for (StringBuilder builder : receivedData) { assertEquals(dataAsHex, builder.toString()); } for (long receivedPing : receivedPings) { assertEquals(pingData, receivedPing); } } finally { // Don't wait for server to close streams setClientGracefulShutdownTime(0); data.release(); } } private void bootstrapEnv(int dataCountDown, int settingsAckCount, int requestCountDown, int trailersCountDown) throws Exception { bootstrapEnv(dataCountDown, settingsAckCount, requestCountDown, trailersCountDown, -1); } private void bootstrapEnv(int dataCountDown, int settingsAckCount, int requestCountDown, int trailersCountDown, int goAwayCountDown) throws Exception { final CountDownLatch prefaceWrittenLatch = new CountDownLatch(1); requestLatch = new CountDownLatch(requestCountDown); serverSettingsAckLatch = new CountDownLatch(settingsAckCount); dataLatch = new CountDownLatch(dataCountDown); trailersLatch = new CountDownLatch(trailersCountDown); goAwayLatch = goAwayCountDown > 0 ? new CountDownLatch(goAwayCountDown) : requestLatch; sb = new ServerBootstrap(); cb = new Bootstrap(); final AtomicReference serverHandlerRef = new AtomicReference(); final CountDownLatch serverInitLatch = new CountDownLatch(1); sb.group(new DefaultEventLoopGroup()); sb.channel(LocalServerChannel.class); sb.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { serverConnectedChannel = ch; ChannelPipeline p = ch.pipeline(); serverFrameCountDown = new FrameCountDown(serverListener, serverSettingsAckLatch, requestLatch, dataLatch, trailersLatch, goAwayLatch); serverHandlerRef.set(new Http2ConnectionHandlerBuilder() .server(true) .frameListener(serverFrameCountDown) .validateHeaders(false) .build()); p.addLast(serverHandlerRef.get()); serverInitLatch.countDown(); } }); cb.group(new DefaultEventLoopGroup()); cb.channel(LocalChannel.class); cb.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new Http2ConnectionHandlerBuilder() .server(false) .frameListener(clientListener) .validateHeaders(false) .gracefulShutdownTimeoutMillis(0) .build()); p.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt == Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE) { prefaceWrittenLatch.countDown(); ctx.pipeline().remove(this); } } }); } }); serverChannel = sb.bind(new LocalAddress(getClass())).sync().channel(); ChannelFuture ccf = cb.connect(serverChannel.localAddress()); assertTrue(ccf.awaitUninterruptibly().isSuccess()); clientChannel = ccf.channel(); assertTrue(prefaceWrittenLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); http2Client = clientChannel.pipeline().get(Http2ConnectionHandler.class); assertTrue(serverInitLatch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); http2Server = serverHandlerRef.get(); } private ChannelHandlerContext ctx() { return clientChannel.pipeline().firstContext(); } private ChannelHandlerContext serverCtx() { return serverConnectedChannel.pipeline().firstContext(); } private ChannelPromise newPromise() { return ctx().newPromise(); } private ChannelPromise serverNewPromise() { return serverCtx().newPromise(); } private static Http2Headers dummyHeaders() { return new DefaultHttp2Headers(false).method(new AsciiString(""GET"")).scheme(new AsciiString(""https"")) .authority(new AsciiString(""example.org"")).path(new AsciiString(""/some/path/resource2"")) .add(randomString(), randomString()); } private static void mockFlowControl(Http2FrameListener listener) throws Http2Exception { doAnswer(new Answer() { @Override public Integer answer(InvocationOnMock invocation) throws Throwable { ByteBuf buf = (ByteBuf) invocation.getArguments()[2]; int padding = (Integer) invocation.getArguments()[3]; return buf.readableBytes() + padding; } }).when(listener).onDataRead(any(ChannelHandlerContext.class), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()); } private void setClientGracefulShutdownTime(final long millis) throws InterruptedException { setGracefulShutdownTime(clientChannel, http2Client, millis); } private void setServerGracefulShutdownTime(final long millis) throws InterruptedException { setGracefulShutdownTime(serverChannel, http2Server, millis); } private static void setGracefulShutdownTime(Channel channel, final Http2ConnectionHandler handler, final long millis) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); runInChannel(channel, new Http2Runnable() { @Override public void run() throws Http2Exception { handler.gracefulShutdownTimeoutMillis(millis); latch.countDown(); } }); assertTrue(latch.await(DEFAULT_AWAIT_TIMEOUT_SECONDS, SECONDS)); } /** * Creates a {@link ByteBuf} of the given length, filled with random bytes. */ private static ByteBuf randomBytes(int length) { final byte[] bytes = new byte[length]; new Random().nextBytes(bytes); return Unpooled.wrappedBuffer(bytes); } } ","serverChildGroup " "// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.android; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; import com.google.devtools.build.lib.analysis.test.ExecutionInfo; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.packages.InputFile; import com.google.devtools.build.lib.rules.android.AndroidDeviceTest.WithPlatforms; import com.google.devtools.build.lib.rules.android.AndroidDeviceTest.WithoutPlatforms; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; /** Tests for {@link AndroidDevice}. */ @RunWith(Suite.class) @SuiteClasses({WithoutPlatforms.class, WithPlatforms.class}) public abstract class AndroidDeviceTest extends AndroidBuildViewTestCase { /** Use legacy toolchain resolution. */ @RunWith(JUnit4.class) public static class WithoutPlatforms extends AndroidDeviceTest {} /** Use platform-based toolchain resolution. */ @RunWith(JUnit4.class) public static class WithPlatforms extends AndroidDeviceTest { @Override protected boolean platformBasedToolchains() { return true; } } private static final String SYSTEM_IMAGE_LABEL = ""//sdk/system_images:emulator_images_android_21_x86""; private static final String SYSTEM_IMAGE_DIRECTORY = ""sdk/system_images/android_21/x86/""; private static final ImmutableList SYSTEM_IMAGE_FILES = ImmutableList.of( SYSTEM_IMAGE_DIRECTORY + ""kernel-qemu"", SYSTEM_IMAGE_DIRECTORY + ""ramdisk.img"", SYSTEM_IMAGE_DIRECTORY + ""system.img.tar.gz"", SYSTEM_IMAGE_DIRECTORY + ""userdata.img.tar.gz""); private static final String SOURCE_PROPERTIES = SYSTEM_IMAGE_DIRECTORY + ""source.properties""; private static final String REQUIRES_KVM = ""requires-kvm""; @Before public void setup() throws Exception { scratch.file( ""sdk/system_images/BUILD"", ""filegroup("", "" name = 'emulator_images_android_21_x86',"", "" srcs = ["", "" 'android_21/x86/kernel-qemu',"", "" 'android_21/x86/ramdisk.img',"", "" 'android_21/x86/source.properties',"", "" 'android_21/x86/system.img.tar.gz',"", "" 'android_21/x86/userdata.img.tar.gz'"", "" ],"", "")""); setBuildLanguageOptions(""--experimental_google_legacy_api""); } private FilesToRunProvider getToolDependency(ConfiguredTarget target, String label) throws Exception { return getDirectPrerequisite(target, ruleClassProvider.getToolsRepository() + label) .getProvider(FilesToRunProvider.class); } private String getToolDependencyExecPathString(ConfiguredTarget target, String label) throws Exception { return getToolDependency(target, label).getExecutable().getExecPathString(); } private String getToolDependencyRunfilesPathString(ConfiguredTarget target, String label) throws Exception { return getToolDependency(target, label).getExecutable().getRunfilesPathString(); } @Test public void testWellFormedDevice() throws Exception { ConfiguredTarget target = scratchConfiguredTarget( ""tools/android/emulated_device"", ""nexus_6"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256"", "")""); Set outputBasenames = new HashSet<>(); for (Artifact outArtifact : getFilesToBuild(target).toList()) { outputBasenames.add(outArtifact.getPath().getBaseName()); } assertWithMessage(""Not generating expected outputs."") .that(outputBasenames) .containsExactly(""nexus_6"", ""userdata_images.dat"", ""emulator-meta-data.pb""); Runfiles runfiles = getDefaultRunfiles(target); ConfiguredTarget xvfbFiles = getDirectPrerequisite( target, ruleClassProvider.getToolsRepository() + ""//tools/android/emulator:xvfb_support""); assertThat(ActionsTestUtil.execPaths(runfiles.getArtifacts())) .containsAtLeast( getToolDependencyExecPathString(xvfbFiles, ""//tools/android/emulator:support_file1""), getToolDependencyExecPathString(xvfbFiles, ""//tools/android/emulator:support_file2"")); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), ""nexus_6_images/userdata_images.dat""); String systemImageString = Joiner.on("" "").join(SYSTEM_IMAGE_FILES); Iterable biosFilesExecPathStrings = Iterables.transform( getToolDependency(target, ""//tools/android/emulator:emulator_x86_bios"") .getFilesToRun() .toList(), (artifact) -> artifact.getExecPathString()); assertWithMessage(""Invalid boot commandline."") .that(action.getArguments()) .containsExactly( getToolDependencyExecPathString(target, ""//tools/android/emulator:unified_launcher""), ""--action=boot"", ""--density=280"", ""--memory=2048"", ""--skin=720x1280"", ""--cache=32"", ""--vm_size=256"", ""--system_images="" + systemImageString, ""--bios_files="" + Joiner.on("","").join(biosFilesExecPathStrings), ""--source_properties_file="" + SOURCE_PROPERTIES, ""--generate_output_dir="" + targetConfig.getBinFragment(RepositoryName.MAIN) + ""/tools/android/emulated_device/nexus_6_images"", ""--adb_static="" + getToolDependencyExecPathString(target, ""//tools/android:adb_static""), ""--emulator_x86="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:emulator_x86""), ""--emulator_arm="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:emulator_arm""), ""--adb="" + getToolDependencyExecPathString(target, ""//tools/android:adb""), ""--mksdcard="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:mksd""), ""--empty_snapshot_fs="" + getToolDependencyExecPathString( target, ""//tools/android/emulator:empty_snapshot_fs""), ""--flag_configured_android_tools"", ""--nocopy_system_images"", ""--single_image_file"", ""--android_sdk_path="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:sdk_path""), ""--platform_apks=""); assertThat(action.getExecutionInfo()).doesNotContainKey(REQUIRES_KVM); assertThat(ActionsTestUtil.execPaths(action.getInputs())) .containsAtLeast( getToolDependencyExecPathString(xvfbFiles, ""//tools/android/emulator:support_file1""), getToolDependencyExecPathString(xvfbFiles, ""//tools/android/emulator:support_file2"")); assertThat(target.get(ExecutionInfo.PROVIDER.getKey())).isNotNull(); ExecutionInfo executionInfo = target.get(ExecutionInfo.PROVIDER); assertThat(executionInfo.getExecutionInfo()).doesNotContainKey(REQUIRES_KVM); TemplateExpansionAction stubAction = (TemplateExpansionAction) getGeneratingAction(getExecutable(target)); String stubContents = stubAction.getFileContents(); assertThat(stubContents) .contains( String.format( ""unified_launcher=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString( target, ""//tools/android/emulator:unified_launcher""))); assertThat(stubContents) .contains( String.format( ""adb=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString(target, ""//tools/android:adb""))); assertThat(stubContents) .contains( String.format( ""adb_static=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString(target, ""//tools/android:adb_static""))); assertThat(stubContents) .contains( String.format( ""emulator_arm=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString( target, ""//tools/android/emulator:emulator_arm""))); assertThat(stubContents) .contains( String.format( ""emulator_x86=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString( target, ""//tools/android/emulator:emulator_x86""))); assertThat(stubContents) .contains(""source_properties_file=\""${WORKSPACE_DIR}/"" + SOURCE_PROPERTIES + ""\""""); assertThat(stubContents).contains(""emulator_system_images=\"""" + systemImageString + ""\""""); } @Test public void testWellFormedDevice_withKvm() throws Exception { ConfiguredTarget target = scratchConfiguredTarget( ""tools/android/emulated_device"", ""nexus_6"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256,"", "" tags = ['requires-kvm']"", "")""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), ""nexus_6_images/userdata_images.dat""); assertThat(action.getExecutionInfo()).containsEntry(REQUIRES_KVM, """"); assertThat(target.get(ExecutionInfo.PROVIDER.getKey())).isNotNull(); assertThat(target.get(ExecutionInfo.PROVIDER).getExecutionInfo()).containsKey(REQUIRES_KVM); } @Test public void testWellFormedDevice_defaultPropertiesPresent() throws Exception { String dummyPropPackage = ""tools/android/emulated_device/data""; String dummyPropFile = ""default.properties""; String dummyPropLabel = String.format(""//%s:%s"", dummyPropPackage, dummyPropFile); scratch.file(String.format(""%s/%s"", dummyPropPackage, dummyPropFile), ""ro.build.id=HiThere""); scratch.file( String.format(""%s/BUILD"", dummyPropPackage), ""exports_files(['default.properties'])""); ConfiguredTarget target = scratchConfiguredTarget( ""tools/android/emulated_device"", ""nexus_6"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256,"", "" default_properties = '"" + dummyPropLabel + ""'"", "")""); Set outputBasenames = new HashSet<>(); for (Artifact outArtifact : getFilesToBuild(target).toList()) { outputBasenames.add(outArtifact.getPath().getBaseName()); } assertWithMessage(""Not generating expected outputs."") .that(outputBasenames) .containsExactly(""nexus_6"", ""userdata_images.dat"", ""emulator-meta-data.pb""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), ""nexus_6_images/userdata_images.dat""); String systemImageString = Joiner.on("" "").join(SYSTEM_IMAGE_FILES); Iterable biosFilesExecPathStrings = Iterables.transform( getToolDependency(target, ""//tools/android/emulator:emulator_x86_bios"") .getFilesToRun() .toList(), Artifact::getExecPathString); assertWithMessage(""Invalid boot commandline."") .that(action.getArguments()) .containsExactly( getToolDependencyExecPathString(target, ""//tools/android/emulator:unified_launcher""), ""--action=boot"", ""--density=280"", ""--memory=2048"", ""--skin=720x1280"", ""--cache=32"", ""--vm_size=256"", ""--system_images="" + systemImageString, ""--bios_files="" + Joiner.on("","").join(biosFilesExecPathStrings), ""--source_properties_file="" + SOURCE_PROPERTIES, ""--generate_output_dir="" + targetConfig.getBinFragment(RepositoryName.MAIN) + ""/tools/android/emulated_device/nexus_6_images"", ""--adb_static="" + getToolDependencyExecPathString(target, ""//tools/android:adb_static""), ""--emulator_x86="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:emulator_x86""), ""--emulator_arm="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:emulator_arm""), ""--adb="" + getToolDependencyExecPathString(target, ""//tools/android:adb""), ""--mksdcard="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:mksd""), ""--empty_snapshot_fs="" + getToolDependencyExecPathString( target, ""//tools/android/emulator:empty_snapshot_fs""), ""--flag_configured_android_tools"", ""--nocopy_system_images"", ""--single_image_file"", ""--android_sdk_path="" + getToolDependencyExecPathString(target, ""//tools/android/emulator:sdk_path""), ""--platform_apks="", ""--default_properties_file="" + String.format(""%s/%s"", dummyPropPackage, dummyPropFile)); TemplateExpansionAction stubAction = (TemplateExpansionAction) getGeneratingAction(getExecutable(target)); String stubContents = stubAction.getFileContents(); assertThat(stubContents) .contains( String.format( ""unified_launcher=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString( target, ""//tools/android/emulator:unified_launcher""))); assertThat(stubContents) .contains( String.format( ""adb=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString(target, ""//tools/android:adb""))); assertThat(stubContents) .contains( String.format( ""adb_static=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString(target, ""//tools/android:adb_static""))); assertThat(stubContents) .contains( String.format( ""emulator_arm=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString( target, ""//tools/android/emulator:emulator_arm""))); assertThat(stubContents) .contains( String.format( ""emulator_x86=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString( target, ""//tools/android/emulator:emulator_x86""))); assertThat(stubContents) .contains(""source_properties_file=\""${WORKSPACE_DIR}/"" + SOURCE_PROPERTIES + ""\""""); assertThat(stubContents).contains(""emulator_system_images=\"""" + systemImageString + ""\""""); assertThat(stubContents) .contains( String.format( ""android_sdk_path=\""${WORKSPACE_DIR}/%s\"""", getToolDependencyRunfilesPathString(target, ""//tools/android/emulator:sdk_path""))); assertThat( target.getProvider(RunfilesProvider.class).getDefaultRunfiles().getArtifacts().toList()) .contains( getToolDependency(target, ""//tools/android/emulator:unified_launcher"").getExecutable()); } @Test public void testPlatformApksFlag_multipleApks() throws Exception { String dummyPlatformApkPackage = ""tools/android/emulated_device/data""; List dummyPlatformApkFiles = Lists.newArrayList(""dummy1.apk"", ""dummy2.apk"", ""dummy3.apk"", ""dummy4.apk""); List platformApkFullPaths = Lists.newArrayList(); List [MASK] s = Lists.newArrayList(); for (String dummyPlatformApkFile : dummyPlatformApkFiles) { String platformApkFullPath = String.format(""%s/%s"", dummyPlatformApkPackage, dummyPlatformApkFile); platformApkFullPaths.add(platformApkFullPath); String [MASK] = String.format(""'//%s:%s'"", dummyPlatformApkPackage, dummyPlatformApkFile); [MASK] s.add( [MASK] ); scratch.file( String.format(""%s/%s"", dummyPlatformApkPackage, dummyPlatformApkFile), dummyPlatformApkFile); } scratch.file( String.format(""%s/BUILD"", dummyPlatformApkPackage), ""exports_files(['dummy1.apk', 'dummy2.apk', 'dummy3.apk', 'dummy4.apk'])""); ConfiguredTarget target = scratchConfiguredTarget( ""tools/android/emulated_device"", ""nexus_6"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256,"", "" platform_apks = ["" + Joiner.on("", "").join( [MASK] s) + ""]"", "")""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), ""nexus_6_images/userdata_images.dat""); assertWithMessage(""Missing platform_apks flag"") .that(action.getArguments()) .contains(""--platform_apks="" + Joiner.on("","").join(platformApkFullPaths)); } @Test public void testPlatformApksFlag() throws Exception { String dummyPlatformApkPackage = ""tools/android/emulated_device/data""; String dummyPlatformApkFile = ""dummy.apk""; String [MASK] = String.format(""//%s:%s"", dummyPlatformApkPackage, dummyPlatformApkFile); scratch.file(String.format(""%s/%s"", dummyPlatformApkPackage, dummyPlatformApkFile), ""dummyApk""); scratch.file( String.format(""%s/BUILD"", dummyPlatformApkPackage), ""exports_files(['dummy.apk'])""); ConfiguredTarget target = scratchConfiguredTarget( ""tools/android/emulated_device"", ""nexus_6"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256,"", "" platform_apks = ['"" + [MASK] + ""']"", "")""); SpawnAction action = (SpawnAction) actionsTestUtil() .getActionForArtifactEndingWith( actionsTestUtil().artifactClosureOf(getFilesToBuild(target)), ""nexus_6_images/userdata_images.dat""); assertWithMessage(""Missing platform_apks flag"") .that(action.getArguments()) .contains( ""--platform_apks="" + String.format(""%s/%s"", dummyPlatformApkPackage, dummyPlatformApkFile)); } @Test public void testBadAttributes() throws Exception { checkError( ""bad/ram"", ""bad_ram"", ""ram must be"", ""android_device(name = 'bad_ram', "", "" ram = -1, "", "" vm_heap = 24, "", "" cache = 123, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 456, "", "" horizontal_resolution = 640, "", "" vertical_resolution = 800) ""); checkError( ""bad/vm"", ""bad_vm"", ""heap must be"", ""android_device(name = 'bad_vm', "", "" ram = 512, "", "" vm_heap = -24, "", "" cache = 123, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 456, "", "" horizontal_resolution = 640, "", "" vertical_resolution = 800) ""); checkError( ""bad/cache"", ""bad_cache"", ""cache must be"", ""android_device(name = 'bad_cache', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = -123, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 456, "", "" horizontal_resolution = 640, "", "" vertical_resolution = 800) ""); checkError( ""bad/density"", ""bad_density"", ""density must be"", ""android_device(name = 'bad_density', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = 23, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = -456, "", "" horizontal_resolution = 640, "", "" vertical_resolution = 800) ""); checkError( ""bad/horizontal"", ""bad_horizontal"", ""horizontal must be"", ""android_device(name = 'bad_horizontal', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = 23, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = -456, "", "" horizontal_resolution = 100, "", "" vertical_resolution = 800) ""); checkError( ""bad/vertical"", ""bad_vertical"", ""vertical must be"", ""android_device(name = 'bad_vertical', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = 23, "", "" screen_density = -456, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" horizontal_resolution = 640, "", "" vertical_resolution = 100) ""); checkError( ""bad/bogus_default_prop"", ""bogus_default_prop"", ""no such package"", ""android_device(name = 'bogus_default_prop', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = 23, "", "" screen_density = 311, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" default_properties = '//something/somewhere',"", "" horizontal_resolution = 640, "", "" vertical_resolution = 100) ""); checkError( ""bad/multi_default_prop"", ""multi_default_prop"", ""expected a single artifact"", ""android_device(name = 'multi_default_prop', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = 23, "", "" screen_density = 311, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" default_properties = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" horizontal_resolution = 640, "", "" vertical_resolution = 100) ""); checkError( ""bad/filegroup"", ""bad_filegroup"", ""No source.properties"", ""filegroup(name = 'empty',"", "" srcs = [])"", ""android_device(name = 'bad_filegroup', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = 23, "", "" screen_density = -456, "", "" system_image = ':empty',"", "" horizontal_resolution = 640, "", "" vertical_resolution = 100) ""); checkError( ""bad/filegroup_2"", ""bad_filegroup2"", ""Multiple source.properties"", ""filegroup(name = 'empty',"", "" srcs = ['source.properties', 'foo/source.properties'])"", ""android_device(name = 'bad_filegroup2', "", "" ram = 512, "", "" vm_heap = 24, "", "" cache = 23, "", "" screen_density = -456, "", "" system_image = ':empty',"", "" horizontal_resolution = 640, "", "" vertical_resolution = 100) ""); } @Test public void testPackageWhitelist() throws Exception { ConfiguredTarget validPackageAndroidDevice = scratchConfiguredTarget( ""foo"", ""nexus_6"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256"", "")""); assertThat(validPackageAndroidDevice).isNotNull(); InputFile mockedAndroidToolsBuildFile = (InputFile) getTarget(ruleClassProvider.getToolsRepository() + ""//tools/android:BUILD""); String mockedAndroidToolsBuildFileLocation = mockedAndroidToolsBuildFile.getPath().getPathString(); String mockedAndroidToolsContent = scratch .readFile(mockedAndroidToolsBuildFileLocation) .replaceAll(Pattern.quote(""packages = ['public']""), ""packages = ['//bar/...']"") // TODO(b/254084490): Migrate Google-internal usage of ""//..."" in test mock to be // ""public"" instead. .replaceAll(Pattern.quote(""packages = ['//...']""), ""packages = ['//bar/...']""); scratch.overwriteFile(mockedAndroidToolsBuildFileLocation, mockedAndroidToolsContent); invalidatePackages(); checkError( ""baz"", ""nexus_6"", ""The android_device rule may not be used in this package"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256"", "")""); } @Test public void testAndroidDeviceBrokerInfoExposedToStarlark() throws Exception { scratch.file( ""tools/android/emulated_device/BUILD"", ""android_device("", "" name = 'nexus_6', "", "" ram = 2048, "", "" horizontal_resolution = 720, "", "" vertical_resolution = 1280, "", "" cache = 32, "", "" system_image = '"" + SYSTEM_IMAGE_LABEL + ""',"", "" screen_density = 280, "", "" vm_heap = 256,"", "" tags = ['requires-kvm']"", "")""); scratch.file( ""javatests/com/app/starlarktest/starlarktest.bzl"", ""mystring = provider(fields = ['content'])"", ""def _impl(ctx):"", "" return [mystring(content = ctx.attr.dep[AndroidDeviceBrokerInfo])]"", ""starlarktest = rule(implementation=_impl, attrs = {'dep': attr.label()})""); scratch.file( ""javatests/com/app/starlarktest/BUILD"", ""load(':starlarktest.bzl', 'starlarktest')"", ""starlarktest(name = 'mytest', dep = '//tools/android/emulated_device:nexus_6')""); ConfiguredTarget ct = getConfiguredTarget(""//javatests/com/app/starlarktest:mytest""); assertThat(ct).isNotNull(); } } ","dummyPlatformApkLabel " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.upstream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; /** * Calculate any percentile over a sliding window of weighted values. A maximum weight is * configured. Once the total weight of the values reaches the maximum weight, the oldest value is * reduced in weight until it reaches zero and is removed. This maintains a constant total weight, * equal to the maximum allowed, at the steady state. * *

This class can be used for bandwidth estimation based on a sliding window of past transfer * rate observations. This is an alternative to sliding mean and exponential averaging which suffer * from susceptibility to outliers and slow adaptation to step functions. * *

See the following Wikipedia articles: * *

* * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public class SlidingPercentile { // Orderings. private static final Comparator INDEX_COMPARATOR = (a, b) -> a.index - b.index; private static final Comparator VALUE_COMPARATOR = (a, b) -> Float.compare(a.value, b.value); private static final int SORT_ORDER_NONE = -1; private static final int SORT_ORDER_BY_VALUE = 0; private static final int SORT_ORDER_BY_INDEX = 1; private static final int MAX_RECYCLED_SAMPLES = 5; private final int maxWeight; private final ArrayList samples; private final Sample[] recycledSamples; private int currentSortOrder; private int nextSampleIndex; private int totalWeight; private int recycledSampleCount; /** * @param maxWeight The maximum weight. */ public SlidingPercentile(int maxWeight) { this.maxWeight = maxWeight; recycledSamples = new Sample[MAX_RECYCLED_SAMPLES]; samples = new ArrayList<>(); currentSortOrder = SORT_ORDER_NONE; } /** Resets the sliding percentile. */ public void reset() { samples.clear(); currentSortOrder = SORT_ORDER_NONE; nextSampleIndex = 0; totalWeight = 0; } /** * Adds a new weighted value. * * @param weight The weight of the new observation. * @param value The value of the new observation. */ public void addSample(int weight, float value) { ensureSortedByIndex(); Sample newSample = recycledSampleCount > 0 ? recycledSamples[--recycledSampleCount] : new Sample(); newSample.index = nextSampleIndex++; newSample.weight = weight; newSample.value = value; samples.add(newSample); totalWeight += weight; while (totalWeight > maxWeight) { int excessWeight = totalWeight - maxWeight; Sample oldestSample = samples.get(0); if (oldestSample.weight <= excessWeight) { totalWeight -= oldestSample.weight; samples.remove(0); if (recycledSampleCount < MAX_RECYCLED_SAMPLES) { recycledSamples[recycledSampleCount++] = oldestSample; } } else { oldestSample.weight -= excessWeight; totalWeight -= excessWeight; } } } /** * Computes a percentile by integration. * * @param percentile The desired percentile, expressed as a fraction in the range (0,1]. * @return The requested percentile value or {@link Float#NaN} if no samples have been added. */ public float getPercentile(float percentile) { ensureSortedByValue(); float [MASK] = percentile * totalWeight; int accumulatedWeight = 0; for (int i = 0; i < samples.size(); i++) { Sample currentSample = samples.get(i); accumulatedWeight += currentSample.weight; if (accumulatedWeight >= [MASK] ) { return currentSample.value; } } // Clamp to maximum value or NaN if no values. return samples.isEmpty() ? Float.NaN : samples.get(samples.size() - 1).value; } /** Sorts the samples by index. */ private void ensureSortedByIndex() { if (currentSortOrder != SORT_ORDER_BY_INDEX) { Collections.sort(samples, INDEX_COMPARATOR); currentSortOrder = SORT_ORDER_BY_INDEX; } } /** Sorts the samples by value. */ private void ensureSortedByValue() { if (currentSortOrder != SORT_ORDER_BY_VALUE) { Collections.sort(samples, VALUE_COMPARATOR); currentSortOrder = SORT_ORDER_BY_VALUE; } } private static class Sample { public int index; public int weight; public float value; } } ","desiredWeight " "/* * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.devtools.j2objc.util; import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.ArrayAccess; import com.google.devtools.j2objc.ast.ArrayCreation; import com.google.devtools.j2objc.ast.ArrayInitializer; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.CastExpression; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.ConditionalExpression; import com.google.devtools.j2objc.ast.EnumDeclaration; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.FieldAccess; import com.google.devtools.j2objc.ast.FunctionInvocation; import com.google.devtools.j2objc.ast.InfixExpression; import com.google.devtools.j2objc.ast.PackageDeclaration; import com.google.devtools.j2objc.ast.ParenthesizedExpression; import com.google.devtools.j2objc.ast.PostfixExpression; import com.google.devtools.j2objc.ast.PrefixExpression; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.TreeNode; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.TypeLiteral; import com.google.devtools.j2objc.types.FunctionElement; import com.google.j2objc.annotations.ReflectionSupport; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; /** * General collection of utility methods. * * @author Keith Stanger, Tom Ball */ public final class TranslationUtil { private final TypeUtil typeUtil; private final NameTable nameTable; private final Options options; private final ElementUtil elementUtil; private final URLClassLoader jreEmulLoader; public TranslationUtil(TypeUtil typeUtil, NameTable nameTable, Options options, ElementUtil elementUtil) { this.typeUtil = typeUtil; this.nameTable = nameTable; this.options = options; this.elementUtil = elementUtil; this.jreEmulLoader = getJreEmulClassPath(options); } public static TypeElement getSuperType(AbstractTypeDeclaration node) { // Use the AST as the source of truth where possible. if (node instanceof TypeDeclaration) { TypeMirror superclassTypeMirror = ((TypeDeclaration) node).getSuperclassTypeMirror(); return superclassTypeMirror == null ? null : TypeUtil.asTypeElement(superclassTypeMirror); } else { return ElementUtil.getSuperclass(node.getTypeElement()); } } public static List getInterfaceTypes(AbstractTypeDeclaration node) { // Use the AST as the source of truth where possible. List astInterfaces = null; if (node instanceof TypeDeclaration) { astInterfaces = ((TypeDeclaration) node).getSuperInterfaceTypeMirrors(); } else if (node instanceof EnumDeclaration) { astInterfaces = ((EnumDeclaration) node).getSuperInterfaceTypeMirrors(); } else { // AnnotationTypeDeclaration return ElementUtil.getInterfaces(node.getTypeElement()); } List result = new ArrayList<>(); for (TypeMirror typeMirror : astInterfaces) { result.add(TypeUtil.asTypeElement(typeMirror)); } return result; } public boolean needsReflection(AbstractTypeDeclaration node) { return needsReflection(node.getTypeElement()); } public boolean needsReflection(PackageDeclaration node) { ReflectionSupport.Level level = getReflectionSupportLevelOnPackage(node.getPackageElement()); return needsReflection(level); } public boolean needsReflection(TypeElement type) { if (ElementUtil.isLambda(type)) { return false; } if (isJUnitTestClass(type) || ElementUtil.isRuntimeAnnotation(type)) { return true; } PackageElement packageElement = ElementUtil.getPackage(type); ReflectionSupport.Level level = null; while (type != null) { level = getReflectionSupportLevel(ElementUtil.getAnnotation(type, ReflectionSupport.class)); if (level != null) { return level == ReflectionSupport.Level.FULL; } type = ElementUtil.getDeclaringClass(type); } // Check package level annotations level = getReflectionSupportLevelOnPackage(packageElement); return needsReflection(level); } private boolean needsReflection(ReflectionSupport.Level level) { if (level != null) { return level == ReflectionSupport.Level.FULL; } else { return !options.stripReflection(); } } private boolean isJUnitTestClass(TypeElement type) { if (ElementUtil.isPackageInfo(type)) { return false; } return isJUnit3TestClass(type) || isJUnit4TestClass(type); } public boolean isJUnit3TestClass(TypeElement type) { TypeElement testType = typeUtil.resolveJavaType(""junit.framework.Test""); return testType != null && typeUtil.isAssignable(type.asType(), testType.asType()); } private boolean isJUnit4TestClass(TypeElement type) { if (ElementUtil.hasQualifiedNamedAnnotation(type, ""org.junit.runner.RunWith"")) { return true; } for (Element e : type.getEnclosedElements()) { if (ElementUtil.hasQualifiedNamedAnnotation(e, ""org.junit.Test"")) { return true; } } return false; } private ReflectionSupport.Level getReflectionSupportLevelOnPackage(PackageElement node) { ReflectionSupport.Level level = getReflectionSupportLevel( ElementUtil.getAnnotation(node, ReflectionSupport.class)); if (level != null) { return level; } // Check if package-info.java contains ReflectionSupport annotation level = options.getPackageInfoLookup().getReflectionSupportLevel( node.getQualifiedName().toString()); return level; } public static ReflectionSupport.Level getReflectionSupportLevel( AnnotationMirror reflectionSupport) { if (reflectionSupport == null) { return null; } VariableElement level = (VariableElement) ElementUtil.getAnnotationValue(reflectionSupport, ""value""); return level != null ? ReflectionSupport.Level.valueOf(level.getSimpleName().toString()) : null; } /** * If possible give this expression an unbalanced extra retain. If a non-null * result is returned, then the returned expression has an unbalanced extra * retain and the passed in expression is removed from the tree and must be * discarded. If null is returned then the passed in expression is left * untouched. The caller must ensure the result is eventually consumed. */ public static Expression retainResult(Expression node) { switch (node.getKind()) { case ARRAY_CREATION: ((ArrayCreation) node).setHasRetainedResult(true); return TreeUtil.remove(node); case CLASS_INSTANCE_CREATION: ((ClassInstanceCreation) node).setHasRetainedResult(true); return TreeUtil.remove(node); case FUNCTION_INVOCATION: { FunctionInvocation invocation = (FunctionInvocation) node; if (invocation.getFunctionElement().getRetainedResultName() != null) { invocation.setHasRetainedResult(true); return TreeUtil.remove(node); } return null; } default: return null; } } public static boolean isAssigned(Expression node) { TreeNode parent = node.getParent(); while (parent instanceof ParenthesizedExpression) { node = (Expression) parent; parent = node.getParent(); } if (parent instanceof PostfixExpression) { PostfixExpression.Operator op = ((PostfixExpression) parent).getOperator(); if (op == PostfixExpression.Operator.INCREMENT || op == PostfixExpression.Operator.DECREMENT) { return true; } } else if (parent instanceof PrefixExpression) { PrefixExpression.Operator op = ((PrefixExpression) parent).getOperator(); if (op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT || op == PrefixExpression.Operator.ADDRESS_OF) { return true; } } else if (parent instanceof Assignment) { return node == ((Assignment) parent).getLeftHandSide(); } return false; } /** * Returns whether the expression might have any side effects. If true, it * would be unsafe to prune the given node from the tree. */ public static boolean hasSideEffect(Expression expr) { VariableElement var = TreeUtil.getVariableElement(expr); if (var != null && ElementUtil.isVolatile(var)) { return true; } switch (expr.getKind()) { case BOOLEAN_LITERAL: case CHARACTER_LITERAL: case NULL_LITERAL: case NUMBER_LITERAL: case QUALIFIED_NAME: case SIMPLE_NAME: case STRING_LITERAL: case SUPER_FIELD_ACCESS: case THIS_EXPRESSION: return false; case CAST_EXPRESSION: return hasSideEffect(((CastExpression) expr).getExpression()); case CONDITIONAL_EXPRESSION: { ConditionalExpression condExpr = (ConditionalExpression) expr; return hasSideEffect(condExpr.getExpression()) || hasSideEffect(condExpr.getThenExpression()) || hasSideEffect(condExpr.getElseExpression()); } case FIELD_ACCESS: return hasSideEffect(((FieldAccess) expr).getExpression()); case INFIX_EXPRESSION: for (Expression operand : ((InfixExpression) expr).getOperands()) { if (hasSideEffect(operand)) { return true; } } return false; case PARENTHESIZED_EXPRESSION: return hasSideEffect(((ParenthesizedExpression) expr).getExpression()); case PREFIX_EXPRESSION: { PrefixExpression preExpr = (PrefixExpression) expr; PrefixExpression.Operator op = preExpr.getOperator(); return op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT || hasSideEffect(preExpr.getOperand()); } default: return true; } } /** * Returns the modifier for an assignment expression being converted to a * function. The result will be ""Array"" if the lhs is an array access, * ""Strong"" if the lhs is a field with a strong reference, and an empty string * for local variables and weak fields. */ public String getOperatorFunctionModifier(Expression expr) { VariableElement var = TreeUtil.getVariableElement(expr); if (var == null) { assert TreeUtil.trimParentheses(expr) instanceof ArrayAccess : ""Expression cannot be resolved to a variable or array access.""; return ""Array""; } String modifier = """"; if (ElementUtil.isVolatile(var)) { modifier += ""Volatile""; } else if (!ElementUtil.isWeakReference(var) && var.getKind().isField() && options.useStrictFieldAssign()) { modifier += ""StrictField""; } if (!ElementUtil.isWeakReference(var) && (var.getKind().isField() || options.useARC())) { modifier += ""Strong""; } return modifier; } public Expression createObjectArray(List expressions, ArrayType arrayType) { if (expressions.isEmpty()) { return new ArrayCreation(arrayType, typeUtil, 0); } ArrayInitializer initializer = new ArrayInitializer(arrayType); initializer.getExpressions().addAll(expressions); return new ArrayCreation(initializer); } public Expression createAnnotation(AnnotationMirror annotationMirror) { DeclaredType type = annotationMirror.getAnnotationType(); TypeElement typeElem = (TypeElement) type.asElement(); FunctionElement element = new FunctionElement(""create_"" + nameTable.getFullName(typeElem), type, typeElem); FunctionInvocation invocation = new FunctionInvocation(element, type); Map values = typeUtil.elementUtil().getElementValuesWithDefaults(annotationMirror); for (ExecutableElement member : ElementUtil.getSortedAnnotationMembers(typeElem)) { TypeMirror valueType = member.getReturnType(); element.addParameters(valueType); invocation.addArgument(createAnnotationValue(valueType, values.get(member))); } return invocation; } public Expression createAnnotationValue(TypeMirror type, AnnotationValue aValue) { Object value = aValue.getValue(); if (value instanceof VariableElement) { return new SimpleName((VariableElement) value); } else if (TypeUtil.isArray(type)) { assert value instanceof List; ArrayType arrayType = (ArrayType) type; @SuppressWarnings(""unchecked"") List list = (List) value; List generatedValues = new ArrayList<>(); for (AnnotationValue elem : list) { generatedValues.add(createAnnotationValue(arrayType.getComponentType(), elem)); } return createObjectArray(generatedValues, arrayType); } else if (TypeUtil.isAnnotation(type)) { assert value instanceof AnnotationMirror; return createAnnotation((AnnotationMirror) value); } else if (value instanceof TypeMirror) { return new TypeLiteral((TypeMirror) value, typeUtil); } else { // Boolean, Character, Number, String return TreeUtil.newLiteral(value, typeUtil); } } /** * Returns true if an implementation for a type element should be generated. * Normally this is true, but in Java 8 a few interfaces from JSR 250 * (https://jcp.org/en/jsr/detail?id=250) were added, causing duplicate * symbol link errors when building an app that uses the other JSR 250 * annotations. javax.annotation defined on the bootclasspath are therefore * ignored, since translating them won't cause link errors later. *

* If the -Xtranslate-bootclasspath flag is specified * (normally only when building the jre_emul libraries), then types * are always generated. */ public boolean generateImplementation(TypeElement typeElement) { if (options.translateBootclasspathFiles()) { return true; } String className = elementUtil.getBinaryName(typeElement).replace('.', '/'); if (!className.startsWith(""javax/annotation/"")) { return true; } String resourcePath = className.replace('.', '/') + "".class""; return jreEmulLoader.findResource(resourcePath) == null; } private URLClassLoader getJreEmulClassPath(Options options) { List [MASK] = new ArrayList<>(); for (String path : options.getBootClasspath()) { if (path.matches(""^.*jre_emul.*jar$"")) { try { [MASK] .add(new File(path).toURI().toURL()); } catch (MalformedURLException e) { // Ignore bad path. } } } return new URLClassLoader( [MASK] .toArray(new URL[0])); } } ","bootURLs " "// line 1 ""XmlReader.rl"" // Do not edit this file! Generated by Ragel. // Ragel.exe -G2 -J -o XmlReader.java XmlReader.rl /******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.utils; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.ObjectMap.Entry; /** Lightweight XML parser. Supports a subset of XML features: elements, attributes, text, predefined entities, CDATA, mixed * content. Namespaces are parsed as part of the element or attribute name. Prologs and doctypes are ignored. Only 8-bit character * encodings are supported. Input is assumed to be well formed.
*
* The default behavior is to parse the XML into a DOM. Extends this class and override methods to perform event driven parsing. * When this is done, the parse methods will return null. * @author Nathan Sweet */ public class XmlReader { private final Array elements = new Array(8); private Element root, current; private final StringBuilder textBuffer = new StringBuilder(64); private String entitiesText; public Element parse (String xml) { char[] data = xml.toCharArray(); return parse(data, 0, data. [MASK] ); } public Element parse (Reader reader) { try { char[] data = new char[1024]; int offset = 0; while (true) { int [MASK] = reader.read(data, offset, data. [MASK] - offset); if ( [MASK] == -1) break; if ( [MASK] == 0) { char[] newData = new char[data. [MASK] * 2]; System.arraycopy(data, 0, newData, 0, data. [MASK] ); data = newData; } else offset += [MASK] ; } return parse(data, 0, offset); } catch (IOException ex) { throw new SerializationException(ex); } finally { StreamUtils.closeQuietly(reader); } } public Element parse (InputStream input) { try { return parse(new InputStreamReader(input, ""UTF-8"")); } catch (IOException ex) { throw new SerializationException(ex); } finally { StreamUtils.closeQuietly(input); } } public Element parse (FileHandle file) { try { return parse(file.reader(""UTF-8"")); } catch (Exception ex) { throw new SerializationException(""Error parsing file: "" + file, ex); } } public Element parse (char[] data, int offset, int [MASK] ) { int cs, p = offset, pe = [MASK] ; int s = 0; String attributeName = null; boolean hasBody = false; // line 3 ""XmlReader.java"" { cs = xml_start; } // line 7 ""XmlReader.java"" { int _klen; int _trans = 0; int _acts; int _nacts; int _keys; int _goto_targ = 0; _goto: while (true) { switch (_goto_targ) { case 0: if (p == pe) { _goto_targ = 4; continue _goto; } if (cs == 0) { _goto_targ = 5; continue _goto; } case 1: _match: do { _keys = _xml_key_offsets[cs]; _trans = _xml_index_offsets[cs]; _klen = _xml_single_ [MASK] s[cs]; if (_klen > 0) { int _lower = _keys; int _mid; int _upper = _keys + _klen - 1; while (true) { if (_upper < _lower) break; _mid = _lower + ((_upper - _lower) >> 1); if (data[p] < _xml_trans_keys[_mid]) _upper = _mid - 1; else if (data[p] > _xml_trans_keys[_mid]) _lower = _mid + 1; else { _trans += (_mid - _keys); break _match; } } _keys += _klen; _trans += _klen; } _klen = _xml_range_ [MASK] s[cs]; if (_klen > 0) { int _lower = _keys; int _mid; int _upper = _keys + (_klen << 1) - 2; while (true) { if (_upper < _lower) break; _mid = _lower + (((_upper - _lower) >> 1) & ~1); if (data[p] < _xml_trans_keys[_mid]) _upper = _mid - 2; else if (data[p] > _xml_trans_keys[_mid + 1]) _lower = _mid + 2; else { _trans += ((_mid - _keys) >> 1); break _match; } } _trans += _klen; } } while (false); _trans = _xml_indicies[_trans]; cs = _xml_trans_targs[_trans]; if (_xml_trans_actions[_trans] != 0) { _acts = _xml_trans_actions[_trans]; _nacts = (int)_xml_actions[_acts++]; while (_nacts-- > 0) { switch (_xml_actions[_acts++]) { case 0: // line 97 ""XmlReader.rl"" { s = p; } break; case 1: // line 98 ""XmlReader.rl"" { char c = data[s]; if (c == '?' || c == '!') { if (data[s + 1] == '[' && // data[s + 2] == 'C' && // data[s + 3] == 'D' && // data[s + 4] == 'A' && // data[s + 5] == 'T' && // data[s + 6] == 'A' && // data[s + 7] == '[') { s += 8; p = s + 2; while (data[p - 2] != ']' || data[p - 1] != ']' || data[p] != '>') p++; text(new String(data, s, p - s - 2)); } else if (c == '!' && data[s + 1] == '-' && data[s + 2] == '-') { p = s + 3; while (data[p] != '-' || data[p + 1] != '-' || data[p + 2] != '>') p++; p += 2; } else while (data[p] != '>') p++; { cs = 15; _goto_targ = 2; if (true) continue _goto; } } hasBody = true; open(new String(data, s, p - s)); } break; case 2: // line 127 ""XmlReader.rl"" { hasBody = false; close(); { cs = 15; _goto_targ = 2; if (true) continue _goto; } } break; case 3: // line 132 ""XmlReader.rl"" { close(); { cs = 15; _goto_targ = 2; if (true) continue _goto; } } break; case 4: // line 136 ""XmlReader.rl"" { if (hasBody) { cs = 15; _goto_targ = 2; if (true) continue _goto; } } break; case 5: // line 139 ""XmlReader.rl"" { attributeName = new String(data, s, p - s); } break; case 6: // line 142 ""XmlReader.rl"" { int end = p; while (end != s) { switch (data[end - 1]) { case ' ': case '\t': case '\n': case '\r': end--; continue; } break; } int current = s; boolean entityFound = false; while (current != end) { if (data[current++] != '&') continue; int entityStart = current; while (current != end) { if (data[current++] != ';') continue; textBuffer.append(data, s, entityStart - s - 1); String name = new String(data, entityStart, current - entityStart - 1); String value = entity(name); textBuffer.append(value != null ? value : name); s = current; entityFound = true; break; } } if (entityFound) { if (s < end) textBuffer.append(data, s, end - s); entitiesText = textBuffer.toString(); textBuffer.setLength(0); } else entitiesText = new String(data, s, end - s); } break; case 7: // line 178 ""XmlReader.rl"" { attribute(attributeName, entitiesText); } break; case 8: // line 181 ""XmlReader.rl"" { text(entitiesText); } break; // line 201 ""XmlReader.java"" } } } case 2: if (cs == 0) { _goto_targ = 5; continue _goto; } if (++p != pe) { _goto_targ = 1; continue _goto; } case 4: case 5: } break; } } // line 195 ""XmlReader.rl"" entitiesText = null; if (p < pe) { int lineNumber = 1; for (int i = 0; i < p; i++) if (data[i] == '\n') lineNumber++; throw new SerializationException( ""Error parsing XML on line "" + lineNumber + "" near: "" + new String(data, p, Math.min(32, pe - p))); } else if (elements.size != 0) { Element element = elements.peek(); elements.clear(); throw new SerializationException(""Error parsing XML, unclosed element: "" + element.getName()); } Element root = this.root; this.root = null; return root; } // line 221 ""XmlReader.java"" private static byte[] init__xml_actions_0 () { return new byte[] {0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 2, 1, 4, 2, 2, 4, 2, 6, 7, 2, 6, 8, 3, 0, 6, 7}; } private static final byte _xml_actions[] = init__xml_actions_0(); private static byte[] init__xml_key_offsets_0 () { return new byte[] {0, 0, 4, 9, 14, 20, 26, 30, 35, 36, 37, 42, 46, 50, 51, 52, 56, 57, 62, 67, 73, 79, 83, 88, 89, 90, 95, 99, 103, 104, 108, 109, 110, 111, 112, 115}; } private static final byte _xml_key_offsets[] = init__xml_key_offsets_0(); private static char[] init__xml_trans_keys_0 () { return new char[] {32, 60, 9, 13, 32, 47, 62, 9, 13, 32, 47, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 61, 9, 13, 32, 34, 39, 9, 13, 34, 34, 32, 47, 62, 9, 13, 32, 62, 9, 13, 32, 62, 9, 13, 39, 39, 32, 60, 9, 13, 60, 32, 47, 62, 9, 13, 32, 47, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 61, 9, 13, 32, 34, 39, 9, 13, 34, 34, 32, 47, 62, 9, 13, 32, 62, 9, 13, 32, 62, 9, 13, 60, 32, 47, 9, 13, 62, 62, 39, 39, 32, 9, 13, 0}; } private static final char _xml_trans_keys[] = init__xml_trans_keys_0(); private static byte[] init__xml_single_ [MASK] s_0 () { return new byte[] {0, 2, 3, 3, 4, 4, 2, 3, 1, 1, 3, 2, 2, 1, 1, 2, 1, 3, 3, 4, 4, 2, 3, 1, 1, 3, 2, 2, 1, 2, 1, 1, 1, 1, 1, 0}; } private static final byte _xml_single_ [MASK] s[] = init__xml_single_ [MASK] s_0(); private static byte[] init__xml_range_ [MASK] s_0 () { return new byte[] {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0}; } private static final byte _xml_range_ [MASK] s[] = init__xml_range_ [MASK] s_0(); private static short[] init__xml_index_offsets_0 () { return new short[] {0, 0, 4, 9, 14, 20, 26, 30, 35, 37, 39, 44, 48, 52, 54, 56, 60, 62, 67, 72, 78, 84, 88, 93, 95, 97, 102, 106, 110, 112, 116, 118, 120, 122, 124, 127}; } private static final short _xml_index_offsets[] = init__xml_index_offsets_0(); private static byte[] init__xml_indicies_0 () { return new byte[] {0, 2, 0, 1, 2, 1, 1, 2, 3, 5, 6, 7, 5, 4, 9, 10, 1, 11, 9, 8, 13, 1, 14, 1, 13, 12, 15, 16, 15, 1, 16, 17, 18, 16, 1, 20, 19, 22, 21, 9, 10, 11, 9, 1, 23, 24, 23, 1, 25, 11, 25, 1, 20, 26, 22, 27, 29, 30, 29, 28, 32, 31, 30, 34, 1, 30, 33, 36, 37, 38, 36, 35, 40, 41, 1, 42, 40, 39, 44, 1, 45, 1, 44, 43, 46, 47, 46, 1, 47, 48, 49, 47, 1, 51, 50, 53, 52, 40, 41, 42, 40, 1, 54, 55, 54, 1, 56, 42, 56, 1, 57, 1, 57, 34, 57, 1, 1, 58, 59, 58, 51, 60, 53, 61, 62, 62, 1, 1, 0}; } private static final byte _xml_indicies[] = init__xml_indicies_0(); private static byte[] init__xml_trans_targs_0 () { return new byte[] {1, 0, 2, 3, 3, 4, 11, 34, 5, 4, 11, 34, 5, 6, 7, 6, 7, 8, 13, 9, 10, 9, 10, 12, 34, 12, 14, 14, 16, 15, 17, 16, 17, 18, 30, 18, 19, 26, 28, 20, 19, 26, 28, 20, 21, 22, 21, 22, 23, 32, 24, 25, 24, 25, 27, 28, 27, 29, 31, 35, 33, 33, 34}; } private static final byte _xml_trans_targs[] = init__xml_trans_targs_0(); private static byte[] init__xml_trans_actions_0 () { return new byte[] {0, 0, 0, 1, 0, 3, 3, 13, 1, 0, 0, 9, 0, 11, 11, 0, 0, 0, 0, 1, 25, 0, 19, 5, 16, 0, 1, 0, 1, 0, 0, 0, 22, 1, 0, 0, 3, 3, 13, 1, 0, 0, 9, 0, 11, 11, 0, 0, 0, 0, 1, 25, 0, 19, 5, 16, 0, 0, 0, 7, 1, 0, 0}; } private static final byte _xml_trans_actions[] = init__xml_trans_actions_0(); static final int xml_start = 1; static final int xml_first_final = 34; static final int xml_error = 0; static final int xml_en_elementBody = 15; static final int xml_en_main = 1; // line 215 ""XmlReader.rl"" protected void open (String name) { Element child = new Element(name, current); Element parent = current; if (parent != null) parent.addChild(child); elements.add(child); current = child; } protected void attribute (String name, String value) { current.setAttribute(name, value); } protected @Null String entity (String name) { if (name.equals(""lt"")) return ""<""; if (name.equals(""gt"")) return "">""; if (name.equals(""amp"")) return ""&""; if (name.equals(""apos"")) return ""'""; if (name.equals(""quot"")) return ""\""""; if (name.startsWith(""#x"")) return Character.toString((char)Integer.parseInt(name.substring(2), 16)); return null; } protected void text (String text) { String existing = current.getText(); current.setText(existing != null ? existing + text : text); } protected void close () { root = elements.pop(); current = elements.size > 0 ? elements.peek() : null; } static public class Element { private final String name; private ObjectMap attributes; private Array children; private String text; private Element parent; public Element (String name, Element parent) { this.name = name; this.parent = parent; } public String getName () { return name; } public ObjectMap getAttributes () { return attributes; } /** @throws GdxRuntimeException if the attribute was not found. */ public String getAttribute (String name) { if (attributes == null) throw new GdxRuntimeException(""Element "" + this.name + "" doesn't have attribute: "" + name); String value = attributes.get(name); if (value == null) throw new GdxRuntimeException(""Element "" + this.name + "" doesn't have attribute: "" + name); return value; } public String getAttribute (String name, String defaultValue) { if (attributes == null) return defaultValue; String value = attributes.get(name); if (value == null) return defaultValue; return value; } public boolean hasAttribute (String name) { if (attributes == null) return false; return attributes.containsKey(name); } public void setAttribute (String name, String value) { if (attributes == null) attributes = new ObjectMap(8); attributes.put(name, value); } public int getChildCount () { if (children == null) return 0; return children.size; } /** @throws GdxRuntimeException if the element has no children. */ public Element getChild (int index) { if (children == null) throw new GdxRuntimeException(""Element has no children: "" + name); return children.get(index); } public void addChild (Element element) { if (children == null) children = new Array(8); children.add(element); } public String getText () { return text; } public void setText (String text) { this.text = text; } public void removeChild (int index) { if (children != null) children.removeIndex(index); } public void removeChild (Element child) { if (children != null) children.removeValue(child, true); } public void remove () { parent.removeChild(this); } public Element getParent () { return parent; } public String toString () { return toString(""""); } public String toString (String indent) { StringBuilder buffer = new StringBuilder(128); buffer.append(indent); buffer.append('<'); buffer.append(name); if (attributes != null) { for (Entry entry : attributes.entries()) { buffer.append(' '); buffer.append(entry.key); buffer.append(""=\""""); buffer.append(entry.value); buffer.append('\""'); } } if (children == null && (text == null || text. [MASK] () == 0)) buffer.append(""/>""); else { buffer.append("">\n""); String childIndent = indent + '\t'; if (text != null && text. [MASK] () > 0) { buffer.append(childIndent); buffer.append(text); buffer.append('\n'); } if (children != null) { for (Element child : children) { buffer.append(child.toString(childIndent)); buffer.append('\n'); } } buffer.append(indent); buffer.append(""'); } return buffer.toString(); } /** @param name the name of the child {@link Element} * @return the first child having the given name or null, does not recurse */ public @Null Element getChildByName (String name) { if (children == null) return null; for (int i = 0; i < children.size; i++) { Element element = children.get(i); if (element.name.equals(name)) return element; } return null; } public boolean hasChild (String name) { if (children == null) return false; return getChildByName(name) != null; } /** @param name the name of the child {@link Element} * @return the first child having the given name or null, recurses */ public @Null Element getChildByNameRecursive (String name) { if (children == null) return null; for (int i = 0; i < children.size; i++) { Element element = children.get(i); if (element.name.equals(name)) return element; Element found = element.getChildByNameRecursive(name); if (found != null) return found; } return null; } public boolean hasChildRecursive (String name) { if (children == null) return false; return getChildByNameRecursive(name) != null; } /** @param name the name of the children * @return the children with the given name or an empty {@link Array} */ public Array getChildrenByName (String name) { Array result = new Array(); if (children == null) return result; for (int i = 0; i < children.size; i++) { Element child = children.get(i); if (child.name.equals(name)) result.add(child); } return result; } /** @param name the name of the children * @return the children with the given name or an empty {@link Array} */ public Array getChildrenByNameRecursively (String name) { Array result = new Array(); getChildrenByNameRecursively(name, result); return result; } private void getChildrenByNameRecursively (String name, Array result) { if (children == null) return; for (int i = 0; i < children.size; i++) { Element child = children.get(i); if (child.name.equals(name)) result.add(child); child.getChildrenByNameRecursively(name, result); } } /** @throws GdxRuntimeException if the attribute was not found. */ public float getFloatAttribute (String name) { return Float.parseFloat(getAttribute(name)); } public float getFloatAttribute (String name, float defaultValue) { String value = getAttribute(name, null); if (value == null) return defaultValue; return Float.parseFloat(value); } /** @throws GdxRuntimeException if the attribute was not found. */ public int getIntAttribute (String name) { return Integer.parseInt(getAttribute(name)); } public int getIntAttribute (String name, int defaultValue) { String value = getAttribute(name, null); if (value == null) return defaultValue; return Integer.parseInt(value); } /** @throws GdxRuntimeException if the attribute was not found. */ public boolean getBooleanAttribute (String name) { return Boolean.parseBoolean(getAttribute(name)); } public boolean getBooleanAttribute (String name, boolean defaultValue) { String value = getAttribute(name, null); if (value == null) return defaultValue; return Boolean.parseBoolean(value); } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public String get (String name) { String value = get(name, null); if (value == null) throw new GdxRuntimeException(""Element "" + this.name + "" doesn't have attribute or child: "" + name); return value; } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public String get (String name, String defaultValue) { if (attributes != null) { String value = attributes.get(name); if (value != null) return value; } Element child = getChildByName(name); if (child == null) return defaultValue; String value = child.getText(); if (value == null) return defaultValue; return value; } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public int getInt (String name) { String value = get(name, null); if (value == null) throw new GdxRuntimeException(""Element "" + this.name + "" doesn't have attribute or child: "" + name); return Integer.parseInt(value); } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public int getInt (String name, int defaultValue) { String value = get(name, null); if (value == null) return defaultValue; return Integer.parseInt(value); } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public float getFloat (String name) { String value = get(name, null); if (value == null) throw new GdxRuntimeException(""Element "" + this.name + "" doesn't have attribute or child: "" + name); return Float.parseFloat(value); } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public float getFloat (String name, float defaultValue) { String value = get(name, null); if (value == null) return defaultValue; return Float.parseFloat(value); } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public boolean getBoolean (String name) { String value = get(name, null); if (value == null) throw new GdxRuntimeException(""Element "" + this.name + "" doesn't have attribute or child: "" + name); return Boolean.parseBoolean(value); } /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. * @throws GdxRuntimeException if no attribute or child was not found. */ public boolean getBoolean (String name, boolean defaultValue) { String value = get(name, null); if (value == null) return defaultValue; return Boolean.parseBoolean(value); } } } ","length " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.classfile.attribute.module; import proguard.classfile.*; import proguard.classfile.constant.visitor.ConstantVisitor; /** * Representation of a Opens entry in a Module attribute. * * @author Joachim Vandersmissen */ public class OpensInfo implements VisitorAccepter { public int u2opensIndex; public int u2opensFlags; public int u2opensToCount; public int[] u2opensToIndex; /** * An extra field in which visitors can store information. */ public Object visitorInfo; /** * Creates an uninitialized OpensInfo. */ public OpensInfo() { } /** * Creates an initialized OpensInfo. */ public OpensInfo(int u2opensIndex, int u2opensFlags, int u2opensToCount, int[] u2opensToIndex) { this.u2opensIndex = u2opensIndex; this.u2opensFlags = u2opensFlags; this.u2opensToCount = u2opensToCount; this.u2opensToIndex = u2opensToIndex; } /** * Applies the given constant pool visitor to the package constant of the * package, if any. */ public void packageAccept(Clazz clazz, ConstantVisitor [MASK] ) { if (u2opensIndex != 0) { clazz.constantPoolEntryAccept(u2opensIndex, [MASK] ); } } /** * Applies the given constant pool visitor to all targets. */ public void targetsAccept(Clazz clazz, ConstantVisitor [MASK] ) { // Loop over all targets. for (int index = 0; index < u2opensToCount; index++) { clazz.constantPoolEntryAccept(u2opensToIndex[index], [MASK] ); } } // Implementations for VisitorAccepter. public Object getVisitorInfo() { return visitorInfo; } public void setVisitorInfo(Object visitorInfo) { this.visitorInfo = visitorInfo; } } ","constantVisitor " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.harmony.tests.java.nio.channels; import android.system.ErrnoException; import libcore.io.OsConstants; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.Inet6Address; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.ClosedChannelException; import java.nio.channels.DatagramChannel; import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnsupportedAddressTypeException; import java.nio.channels.spi.SelectorProvider; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; import libcore.io.IoUtils; import libcore.io.Libcore; /** * Test for DatagramChannel */ public class DatagramChannelTest extends TestCase { private static final int CAPACITY_NORMAL = 200; private static final int CAPACITY_1KB = 1024; private static final int CAPACITY_64KB = 65536; private static final int CAPACITY_ZERO = 0; private static final int CAPACITY_ONE = 1; private static final int TIME_UNIT = 500; private InetSocketAddress datagramSocket1Address; private InetSocketAddress datagramSocket2Address; private InetSocketAddress channel1Address; private InetSocketAddress channel2Address; private DatagramChannel channel1; private DatagramChannel channel2; private DatagramSocket datagramSocket1; private DatagramSocket datagramSocket2; protected void setUp() throws Exception { super.setUp(); channel1 = DatagramChannel.open(); channel2 = DatagramChannel.open(); channel1.socket().bind(new InetSocketAddress(Inet6Address.LOOPBACK, 0)); channel2.socket().bind(new InetSocketAddress(Inet6Address.LOOPBACK, 0)); channel1Address = (InetSocketAddress) channel1.socket().getLocalSocketAddress(); channel2Address = (InetSocketAddress) channel2.socket().getLocalSocketAddress(); this.datagramSocket1 = new DatagramSocket(0, Inet6Address.LOOPBACK); this.datagramSocket2 = new DatagramSocket(0, Inet6Address.LOOPBACK); datagramSocket1Address = (InetSocketAddress) datagramSocket1.getLocalSocketAddress(); datagramSocket2Address = (InetSocketAddress) datagramSocket2.getLocalSocketAddress(); } protected void tearDown() throws Exception { IoUtils.closeQuietly(channel1); IoUtils.closeQuietly(channel2); IoUtils.closeQuietly(datagramSocket1); IoUtils.closeQuietly(datagramSocket2); datagramSocket1Address = null; datagramSocket2Address = null; super.tearDown(); } // ------------------------------------------------------------------- // Test for methods in abstract class. // ------------------------------------------------------------------- /* * Test method for 'java.nio.channels.DatagramChannel.validOps()' */ public void testValidOps() { MockDatagramChannel testMock = new MockDatagramChannel(SelectorProvider .provider()); MockDatagramChannel testMocknull = new MockDatagramChannel(null); int val = this.channel1.validOps(); assertEquals(5, val); assertEquals(val, testMock.validOps()); assertEquals(val, testMocknull.validOps()); } /* * Test method for 'java.nio.channels.DatagramChannel.open()' */ public void testOpen() { MockDatagramChannel testMock = new MockDatagramChannel(SelectorProvider .provider()); MockDatagramChannel testMocknull = new MockDatagramChannel(null); assertNull(testMocknull.provider()); assertNotNull(testMock.provider()); assertEquals(this.channel1.provider(), testMock.provider()); assertEquals(5, testMock.validOps()); } /* * Test method for 'java.nio.channels.DatagramChannel.read(ByteBuffer)' */ public void testReadByteBufferArray() throws IOException { final int testNum = 0; MockDatagramChannel testMock = new MockDatagramChannel(SelectorProvider .provider()); MockDatagramChannel testMocknull = new MockDatagramChannel(null); int bufSize = 10; ByteBuffer[] readBuf = null; try { this.channel1.read(readBuf); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } long readres; try { readres = testMock.read(readBuf); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } readBuf = new ByteBuffer[bufSize]; try { readres = this.channel1.read(readBuf); fail(""Should throw NotYetConnectedException""); } catch (NotYetConnectedException expected) { } readres = testMock.read(readBuf); assertEquals(testNum, readres); readres = testMocknull.read(readBuf); assertEquals(testNum, readres); } /* * Test method for 'java.nio.channels.DatagramChannel.read(ByteBuffer)' */ public void testReadByteBufferArray_BufNull() throws IOException { MockDatagramChannel testMock = new MockDatagramChannel(SelectorProvider .provider()); MockDatagramChannel testMocknull = new MockDatagramChannel(null); ByteBuffer[] readBuf = null; try { this.channel1.read(readBuf); fail(""Should throw NPE""); } catch (NullPointerException expected) { } try { testMock.read(readBuf); fail(""Should throw NPE""); } catch (NullPointerException expected) { } try { testMocknull.read(readBuf); fail(""Should throw NPE""); } catch (NullPointerException expected) { } } /* * Test method for 'java.nio.channels.DatagramChannel.write(ByteBuffer)' */ public void testWriteByteBuffer() throws IOException { MockDatagramChannel testMock = new MockDatagramChannel(SelectorProvider .provider()); MockDatagramChannel testMocknull = new MockDatagramChannel(null); int bufSize = 10; ByteBuffer[] readBuf = null; try { this.channel1.write(readBuf); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } try { testMock.write(readBuf); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } readBuf = new ByteBuffer[bufSize]; try { this.channel1.write(readBuf); fail(""Should throw NotYetConnectedException""); } catch (NotYetConnectedException e) { // correct } long writeres = 0; writeres = testMock.write(readBuf); assertEquals(0, writeres); writeres = testMocknull.write(readBuf); assertEquals(0, writeres); } /* * Test method for 'java.nio.channels.DatagramChannel.write(ByteBuffer)' */ public void testWriteByteBuffer_Bufnull() throws IOException { MockDatagramChannel testMock = new MockDatagramChannel(SelectorProvider .provider()); MockDatagramChannel testMocknull = new MockDatagramChannel(null); ByteBuffer[] readBuf = null; try { this.channel1.write(readBuf); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } try { testMock.write(readBuf); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } try { testMocknull.write(readBuf); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } } // ------------------------------------------------------------------- // Test for socket() // ------------------------------------------------------------------- /** * Test method for 'DatagramChannelImpl.socket()' */ public void testSocket_BasicStatusBeforeConnect() throws Exception { final DatagramChannel dc = DatagramChannel.open(); assertFalse(dc.isConnected());// not connected DatagramSocket s1 = dc.socket(); assertFalse(s1.isBound()); assertFalse(s1.isClosed()); assertFalse(s1.isConnected()); assertFalse(s1.getBroadcast()); assertFalse(s1.getReuseAddress()); assertNull(s1.getInetAddress()); assertTrue(s1.getLocalAddress().isAnyLocalAddress()); assertEquals(s1.getLocalPort(), 0); assertNull(s1.getLocalSocketAddress()); assertEquals(s1.getPort(), -1); assertTrue(s1.getReceiveBufferSize() >= 8192); assertNull(s1.getRemoteSocketAddress()); assertFalse(s1.getReuseAddress()); assertTrue(s1.getSendBufferSize() >= 8192); assertEquals(s1.getSoTimeout(), 0); assertEquals(s1.getTrafficClass(), 0); DatagramSocket s2 = dc.socket(); // same assertSame(s1, s2); dc.close(); } /** * Test method for 'DatagramChannelImpl.socket()' */ public void testSocket_Block_BasicStatusAfterConnect() throws IOException { final DatagramChannel dc = DatagramChannel.open(); dc.connect(datagramSocket1Address); DatagramSocket s1 = dc.socket(); assertSocketAfterConnect(s1); DatagramSocket s2 = dc.socket(); // same assertSame(s1, s2); dc.close(); } public void testSocket_NonBlock_BasicStatusAfterConnect() throws IOException { final DatagramChannel dc = DatagramChannel.open(); dc.connect(datagramSocket1Address); dc.configureBlocking(false); DatagramSocket s1 = dc.socket(); assertSocketAfterConnect(s1); DatagramSocket s2 = dc.socket(); // same assertSame(s1, s2); dc.close(); } private void assertSocketAfterConnect(DatagramSocket s) throws SocketException { assertTrue(s.isBound()); assertFalse(s.isClosed()); assertTrue(s.isConnected()); assertFalse(s.getBroadcast()); assertFalse(s.getReuseAddress()); assertNotNull(s.getLocalSocketAddress()); assertEquals(s.getPort(), datagramSocket1Address.getPort()); assertTrue(s.getReceiveBufferSize() >= 8192); // not same , but equals assertNotSame(s.getRemoteSocketAddress(), datagramSocket1Address); assertEquals(s.getRemoteSocketAddress(), datagramSocket1Address); assertFalse(s.getReuseAddress()); assertTrue(s.getSendBufferSize() >= 8192); assertEquals(s.getSoTimeout(), 0); assertEquals(s.getTrafficClass(), 0); } /** * Test method for 'DatagramChannelImpl.socket()' */ public void testSocket_ActionsBeforeConnect() throws IOException { assertFalse(channel1.isConnected());// not connected assertTrue(channel1.isBlocking()); DatagramSocket s = channel1.socket(); s.connect(datagramSocket2Address); assertTrue(channel1.isConnected()); assertTrue(s.isConnected()); s.disconnect(); assertFalse(channel1.isConnected()); assertFalse(s.isConnected()); s.close(); assertTrue(s.isClosed()); assertFalse(channel1.isOpen()); } /** * Test method for 'DatagramChannelImpl.socket()' */ public void testSocket_Block_ActionsAfterConnect() throws IOException { assertFalse(this.channel1.isConnected());// not connected this.channel1.connect(datagramSocket1Address); DatagramSocket s = this.channel1.socket(); assertSocketActionAfterConnect(s); } public void testSocket_NonBlock_ActionsAfterConnect() throws IOException { this.channel1.connect(datagramSocket1Address); this.channel1.configureBlocking(false); DatagramSocket s = this.channel1.socket(); assertSocketActionAfterConnect(s); } private void assertSocketActionAfterConnect(DatagramSocket s) throws IOException { assertEquals(s.getPort(), datagramSocket1Address.getPort()); try { s.connect(datagramSocket2Address); fail(); } catch (IllegalStateException expected) { } assertTrue(this.channel1.isConnected()); assertTrue(s.isConnected()); // not changed assertEquals(s.getPort(), datagramSocket1Address.getPort()); s.disconnect(); assertFalse(this.channel1.isConnected()); assertFalse(s.isConnected()); s.close(); assertTrue(s.isClosed()); assertFalse(this.channel1.isOpen()); } // ------------------------------------------------------------------- // Test for isConnected() // ------------------------------------------------------------------- /** * Test method for 'DatagramChannelImpl.isConnected()' */ public void testIsConnected_WithServer() throws IOException { connectLocalServer(); disconnectAfterConnected(); this.datagramSocket1.close(); this.channel1.close(); assertFalse(this.channel1.isConnected()); } // ------------------------------------------------------------------- // Test for connect() // ------------------------------------------------------------------- /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_BlockWithServer() throws IOException { // blocking mode assertTrue(this.channel1.isBlocking()); connectLocalServer(); datagramSocket1.close(); disconnectAfterConnected(); } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_BlockNoServer() throws IOException { connectWithoutServer(); disconnectAfterConnected(); } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_NonBlockWithServer() throws IOException { // Non blocking mode this.channel1.configureBlocking(false); connectLocalServer(); datagramSocket1.close(); disconnectAfterConnected(); } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_Null() throws IOException { assertFalse(this.channel1.isConnected()); try { this.channel1.connect(null); fail(""Should throw an IllegalArgumentException here.""); //$NON-NLS-1$ } catch (IllegalArgumentException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_UnsupportedType() throws IOException { assertFalse(this.channel1.isConnected()); class SubSocketAddress extends SocketAddress { private static final long serialVersionUID = 1L; public SubSocketAddress() { super(); } } SocketAddress newTypeAddress = new SubSocketAddress(); try { this.channel1.connect(newTypeAddress); fail(""Should throw an UnsupportedAddressTypeException here.""); } catch (UnsupportedAddressTypeException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_Unresolved() throws IOException { assertFalse(this.channel1.isConnected()); InetSocketAddress unresolved = new InetSocketAddress( ""unresolved address"", 1080); try { this.channel1.connect(unresolved); fail(""Should throw an UnresolvedAddressException here.""); //$NON-NLS-1$ } catch (UnresolvedAddressException e) { // OK. } } public void testConnect_EmptyHost() throws Exception { assertFalse(this.channel1.isConnected()); assertEquals(this.channel1, this.channel1 .connect(new InetSocketAddress("""", 1081))); } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_ClosedChannelException() throws IOException { assertFalse(this.channel1.isConnected()); this.channel1.close(); assertFalse(this.channel1.isOpen()); try { this.channel1.connect(datagramSocket1Address); fail(""Should throw ClosedChannelException.""); //$NON-NLS-1$ } catch (ClosedChannelException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_IllegalStateException() throws IOException { assertFalse(this.channel1.isConnected()); this.channel1.connect(datagramSocket1Address); assertTrue(this.channel1.isConnected()); // connect after connected. try { this.channel1.connect(datagramSocket1Address); fail(""Should throw IllegalStateException.""); //$NON-NLS-1$ } catch (IllegalStateException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.connect(SocketAddress)' */ public void testConnect_CheckOpenBeforeStatus() throws IOException { assertFalse(this.channel1.isConnected()); this.channel1.connect(datagramSocket1Address); assertTrue(this.channel1.isConnected()); // connect after connected. this.channel1.close(); assertFalse(this.channel1.isOpen()); // checking open is before checking status. try { this.channel1.connect(datagramSocket1Address); fail(""Should throw ClosedChannelException.""); //$NON-NLS-1$ } catch (ClosedChannelException e) { // OK. } } private void disconnectAfterConnected() throws IOException { assertTrue(this.channel1.isConnected()); this.channel1.disconnect(); assertFalse(this.channel1.isConnected()); } private void disconnectAfterClosed() throws IOException { assertFalse(this.channel1.isOpen()); assertFalse(this.channel1.isConnected()); this.channel1.disconnect(); assertFalse(this.channel1.isConnected()); } private void connectLocalServer() throws IOException { assertFalse(this.channel1.isConnected()); assertTrue(this.datagramSocket1.isBound()); assertSame(this.channel1, this.channel1.connect(datagramSocket1Address)); assertTrue(this.channel1.isConnected()); } private void connectWithoutServer() throws IOException { assertFalse(this.channel1.isConnected()); this.datagramSocket1.close(); assertTrue(this.datagramSocket1.isClosed()); assertSame(this.channel1, this.channel1.connect(datagramSocket1Address)); assertTrue(this.channel1.isConnected()); } // ------------------------------------------------------------------- // Test for disconnect() // ------------------------------------------------------------------- /** * Test method for 'DatagramChannelImpl.disconnect()' */ public void testDisconnect_BeforeConnect() throws IOException { assertFalse(this.channel1.isConnected()); assertEquals(this.channel1, this.channel1.disconnect()); assertFalse(this.channel1.isConnected()); } /** * Test method for 'DatagramChannelImpl.disconnect()' */ public void testDisconnect_UnconnectedClosed() throws IOException { assertFalse(this.channel1.isConnected()); this.channel1.close(); assertFalse(this.channel1.isOpen()); assertEquals(this.channel1, this.channel1.disconnect()); assertFalse(this.channel1.isConnected()); } /** * Test method for 'DatagramChannelImpl.disconnect()' */ public void testDisconnect_BlockWithServerChannelClosed() throws IOException { assertTrue(this.channel1.isBlocking()); connectLocalServer(); // disconnect after channel close this.channel1.close(); disconnectAfterClosed(); } /** * Test method for 'DatagramChannelImpl.disconnect()' */ public void testDisconnect_NonBlockWithServerChannelClosed() throws IOException { this.channel1.configureBlocking(false); connectLocalServer(); // disconnect after channel close this.channel1.close(); disconnectAfterClosed(); } /** * Test method for 'DatagramChannelImpl.disconnect()' */ public void testDisconnect_BlockWithServerServerClosed() throws IOException { assertTrue(this.channel1.isBlocking()); connectLocalServer(); // disconnect after server close this.datagramSocket1.close(); assertTrue(this.channel1.isOpen()); assertTrue(this.channel1.isConnected()); disconnectAfterConnected(); } /** * Test method for 'DatagramChannelImpl.disconnect()' */ public void testDisconnect_NonBlockWithServerServerClosed() throws IOException { this.channel1.configureBlocking(false); assertFalse(this.channel1.isBlocking()); connectLocalServer(); // disconnect after server close this.datagramSocket1.close(); assertTrue(this.channel1.isOpen()); assertTrue(this.channel1.isConnected()); disconnectAfterConnected(); } // ------------------------------------------------------------------- // Test for receive(): Behavior Without Server. // ------------------------------------------------------------------- /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_UnconnectedNull() throws Exception { assertFalse(this.channel1.isConnected()); try { this.channel1.receive(null); fail(""Should throw a NPE here.""); //$NON-NLS-1$ } catch (NullPointerException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_UnconnectedReadonly() throws Exception { assertFalse(this.channel1.isConnected()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL) .asReadOnlyBuffer(); assertTrue(dst.isReadOnly()); try { this.channel1.receive(dst); fail(""Should throw an IllegalArgumentException here.""); //$NON-NLS-1$ } catch (IllegalArgumentException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_UnconnectedBufEmpty() throws Exception { this.channel1.configureBlocking(false); assertFalse(this.channel1.isConnected()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL); assertNull(this.channel1.receive(dst)); } public void testReceive_UnboundBufZero() throws Exception { DatagramChannel dc = DatagramChannel.open(); assertFalse(dc.isConnected()); assertFalse(dc.socket().isBound()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_ZERO); assertNull(dc.receive(dst)); dc.close(); } public void testReceive_UnboundBufNotEmpty() throws Exception { DatagramChannel dc = DatagramChannel.open(); assertFalse(dc.isConnected()); assertFalse(dc.socket().isBound()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL); // buf is not empty dst.put((byte) 88); assertEquals(dst.position() + CAPACITY_NORMAL - 1, dst.limit()); assertNull(dc.receive(dst)); dc.close(); } public void testReceive_UnboundBufFull() throws Exception { DatagramChannel dc = DatagramChannel.open(); assertFalse(dc.isConnected()); assertFalse(dc.socket().isBound()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_ONE); // buf is full dst.put((byte) 88); assertEquals(dst.position(), dst.limit()); assertNull(dc.receive(dst)); dc.close(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_UnconnectedClose() throws Exception { assertFalse(this.channel1.isConnected()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL); this.channel1.close(); assertFalse(this.channel1.isOpen()); try { assertNull(this.channel1.receive(dst)); fail(""Should throw a ClosedChannelException here.""); //$NON-NLS-1$ } catch (ClosedChannelException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_UnconnectedCloseNull() throws Exception { assertFalse(this.channel1.isConnected()); this.channel1.close(); assertFalse(this.channel1.isOpen()); // checking buffer before checking open try { this.channel1.receive(null); fail(""Should throw a NPE here.""); //$NON-NLS-1$ } catch (NullPointerException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_UnconnectedCloseReadonly() throws Exception { assertFalse(this.channel1.isConnected()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL) .asReadOnlyBuffer(); assertTrue(dst.isReadOnly()); this.channel1.close(); assertFalse(this.channel1.isOpen()); try { this.channel1.receive(dst); fail(""Should throw an IllegalArgumentException here.""); //$NON-NLS-1$ } catch (IllegalArgumentException e) { // OK. } } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerBufEmpty() throws Exception { this.channel1.configureBlocking(false); receiveNonBlockNoServer(CAPACITY_NORMAL); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_BlockNoServerNull() throws Exception { assertTrue(this.channel1.isBlocking()); receiveNoServerNull(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerNull() throws Exception { this.channel1.configureBlocking(false); receiveNoServerNull(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_BlockNoServerReadonly() throws Exception { assertTrue(this.channel1.isBlocking()); receiveNoServerReadonly(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerReadonly() throws Exception { this.channel1.configureBlocking(false); receiveNoServerReadonly(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerBufZero() throws Exception { this.channel1.configureBlocking(false); receiveNonBlockNoServer(CAPACITY_ZERO); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerBufNotEmpty() throws Exception { this.channel1.configureBlocking(false); connectWithoutServer(); ByteBuffer dst = allocateNonEmptyBuf(); assertNull(this.channel1.receive(dst)); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerBufFull() throws Exception { this.channel1.configureBlocking(false); connectWithoutServer(); ByteBuffer dst = allocateFullBuf(); assertNull(this.channel1.receive(dst)); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_BlockNoServerChannelClose() throws Exception { assertTrue(this.channel1.isBlocking()); receiveNoServerChannelClose(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerChannelClose() throws Exception { this.channel1.configureBlocking(false); receiveNoServerChannelClose(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_BlockNoServerCloseNull() throws Exception { assertTrue(this.channel1.isBlocking()); receiveNoServerChannelCloseNull(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerCloseNull() throws Exception { this.channel1.configureBlocking(false); receiveNoServerChannelCloseNull(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_NonBlockNoServerCloseReadonly() throws Exception { this.channel1.configureBlocking(false); receiveNoServerChannelCloseReadonly(); } /** * Test method for 'DatagramChannelImpl.receive(ByteBuffer)' */ public void testReceive_BlockNoServerCloseReadonly() throws Exception { assertTrue(this.channel1.isBlocking()); receiveNoServerChannelCloseReadonly(); } private void receiveNoServerNull() throws IOException { connectWithoutServer(); try { this.channel1.receive(null); fail(""Should throw a NPE here.""); //$NON-NLS-1$ } catch (NullPointerException e) { // OK. } } private void receiveNoServerReadonly() throws IOException { connectWithoutServer(); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL) .asReadOnlyBuffer(); assertTrue(dst.isReadOnly()); try { this.channel1.receive(dst); fail(""Should throw an IllegalArgumentException here.""); //$NON-NLS-1$ } catch (IllegalArgumentException e) { // OK. } } private void receiveNonBlockNoServer(int size) throws IOException { connectWithoutServer(); ByteBuffer dst = ByteBuffer.allocateDirect(size); assertNull(this.channel1.receive(dst)); } private void receiveNoServerChannelClose() throws IOException { connectWithoutServer(); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL); this.channel1.close(); assertFalse(this.channel1.isOpen()); try { assertNull(this.channel1.receive(dst)); fail(""Should throw a ClosedChannelException here.""); //$NON-NLS-1$ } catch (ClosedChannelException e) { // OK. } } private void receiveNoServerChannelCloseNull() throws IOException { connectWithoutServer(); this.channel1.close(); assertFalse(this.channel1.isOpen()); try { this.channel1.receive(null); fail(""Should throw a NPE here.""); //$NON-NLS-1$ } catch (NullPointerException e) { // OK. } } private void receiveNoServerChannelCloseReadonly() throws IOException { connectWithoutServer(); this.channel1.close(); assertFalse(this.channel1.isOpen()); ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL) .asReadOnlyBuffer(); assertTrue(dst.isReadOnly()); try { this.channel1.receive(dst); fail(""Should throw an IllegalArgumentException here.""); //$NON-NLS-1$ } catch (IllegalArgumentException e) { // OK. } } private ByteBuffer allocateFullBuf() { ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_ONE); // buf is full dst.put((byte) 88); assertEquals(dst.position(), dst.limit()); return dst; } private ByteBuffer allocateNonEmptyBuf() { ByteBuffer dst = ByteBuffer.allocateDirect(CAPACITY_NORMAL); // buf is not empty dst.put((byte) 88); dst.put((byte) 99); assertEquals(dst.position() + CAPACITY_NORMAL - 2, dst.limit()); return dst; } // ------------------------------------------------------------------- // Test for send(): Behavior without server. // ------------------------------------------------------------------- private void sendDataBlocking(InetSocketAddress addr, ByteBuffer writeBuf) throws IOException { InetSocketAddress ipAddr = addr; assertEquals(CAPACITY_NORMAL, this.channel1.send(writeBuf, ipAddr)); assertTrue(this.channel1.isOpen()); assertTrue(this.channel1.isBlocking()); this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); } private void sendDataNonBlocking(InetSocketAddress addr, ByteBuffer writeBuf) throws IOException { InetSocketAddress ipAddr = addr; this.channel1.configureBlocking(false); assertEquals(CAPACITY_NORMAL, this.channel1.send(writeBuf, ipAddr)); assertTrue(this.channel1.isOpen()); assertFalse(this.channel1.isBlocking()); this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); } /* * Test method for 'DatagramChannelImpl.send(ByteBuffer, SocketAddress)' */ public void testSend_NoServerBlockingCommon() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); sendDataBlocking(datagramSocket1Address, writeBuf); } public void testSend_NoServerNonblockingCommon() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); sendDataNonBlocking(datagramSocket1Address, writeBuf); } public void testSend_NoServerTwice() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); sendDataBlocking(datagramSocket1Address, writeBuf); // can not buffer twice! assertEquals(0, this.channel1.send(writeBuf, datagramSocket1Address)); try { channel1.send(writeBuf, datagramSocket2Address); fail(""Should throw IllegalArgumentException""); } catch (IllegalArgumentException e) { // correct } } public void testSend_NoServerNonBlockingTwice() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); sendDataNonBlocking(datagramSocket1Address, writeBuf); // can not buffer twice! assertEquals(0, this.channel1.send(writeBuf, datagramSocket1Address)); try { channel1.send(writeBuf, datagramSocket2Address); fail(""Should throw IllegalArgumentException""); } catch (IllegalArgumentException e) { // correct } } public void testSend_NoServerBufNull() throws IOException { try { sendDataBlocking(datagramSocket1Address, null); fail(""Should throw a NPE here.""); } catch (NullPointerException e) { // correct } } public void testSend_NoServerBufNullTwice() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); try { sendDataBlocking(datagramSocket1Address, null); fail(""Should throw a NPE here.""); } catch (NullPointerException e) { // correct } sendDataBlocking(datagramSocket1Address, writeBuf); try { channel1.send(null, datagramSocket2Address); fail(""Should throw NPE""); } catch (NullPointerException e) { // correct } } public void testSend_NoServerAddrNull() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); try { sendDataBlocking(null, writeBuf); fail(""Should throw a IAE here.""); } catch (IllegalArgumentException expected) { } } public void testSend_NoServerAddrNullTwice() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); try { sendDataBlocking(null, writeBuf); fail(""Should throw a IAE here.""); } catch (IllegalArgumentException expected) { } sendDataBlocking(datagramSocket1Address, writeBuf); try { channel1.send(writeBuf, null); fail(""Should throw a IAE here.""); } catch (IllegalArgumentException expected) { } } // ------------------------------------------------------------------- // Test for receive()and send(): Send and Receive with Real Data // ------------------------------------------------------------------- public void testReceiveSend_Block_Normal() throws Exception { sendOnChannel2(""some normal string in testReceiveSend_Normal"", channel1Address); receiveOnChannel1AndClose(CAPACITY_NORMAL, channel2Address, ""some normal string in testReceiveSend_Normal""); } public void testReceiveSend_NonBlock_NotBound() throws Exception { // not bound this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); sendOnChannel2(""some normal string in testReceiveSend_Normal"", datagramSocket2Address); ByteBuffer buf = ByteBuffer.wrap(new byte[CAPACITY_NORMAL]); assertNull(this.channel1.receive(buf)); } public void testReceiveSend_Block_Normal_S2C() throws Exception { sendOnDatagramSocket1( ""some normal string in testReceiveSend_Normal_S2C"", channel1Address); receiveOnChannel1AndClose(CAPACITY_NORMAL, datagramSocket1Address, ""some normal string in testReceiveSend_Normal_S2C""); } public void testReceiveSend_Block_Normal_C2S() throws Exception { String str1 = ""some normal string in testReceiveSend_Normal_C2S""; sendOnChannel2(str1, datagramSocket1Address); receiveOnDatagramSocket1(CAPACITY_NORMAL, str1); } public void testReceiveSend_NonBlock_Normal_C2S() throws Exception { this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); String str1 = ""some normal string in testReceiveSend_Normal_C2S""; sendOnChannel2(str1, datagramSocket1Address); receiveOnDatagramSocket1(CAPACITY_NORMAL, str1); } public void testReceiveSend_Normal_S2S() throws Exception { String msg = ""normal string in testReceiveSend_Normal_S2S""; DatagramPacket rdp = new DatagramPacket(msg.getBytes(), msg.length(), datagramSocket2Address); this.datagramSocket1.send(rdp); byte[] buf = new byte[CAPACITY_NORMAL]; this.datagramSocket2.setSoTimeout(TIME_UNIT); rdp = new DatagramPacket(buf, buf.length); this.datagramSocket2.receive(rdp); assertEquals(new String(buf, 0, CAPACITY_NORMAL).trim(), msg); } public void testReceiveSend_Block_Empty() throws Exception { sendOnChannel2("""", channel1Address); receiveOnChannel1AndClose(CAPACITY_NORMAL, channel2Address, """"); } public void testReceiveSend_NonBlock_Empty() throws Exception { this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); sendOnChannel2("""", channel1Address); receiveOnChannel1AndClose(CAPACITY_NORMAL, channel2Address, """"); } public void testReceiveSend_Block_Empty_S2C() throws Exception { sendOnDatagramSocket1("""", channel1Address); receiveOnChannel1AndClose(CAPACITY_NORMAL, datagramSocket1Address, """"); } public void testReceiveSend_NonBlock_Empty_S2C() throws Exception { this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); sendOnDatagramSocket1("""", channel1Address); receiveOnChannel1AndClose(CAPACITY_NORMAL, datagramSocket1Address, """"); } /** iOS poll of empty datagram fails: Apple Radar #47594701 public void testReceiveSend_Block_Empty_C2S() throws Exception { sendOnChannel2("""", datagramSocket1Address); receiveOnDatagramSocket1(CAPACITY_NORMAL, """"); } public void testReceiveSend_NonBlock_Empty_C2S() throws Exception { this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); sendOnChannel2("""", datagramSocket1Address); receiveOnDatagramSocket1(CAPACITY_NORMAL, """"); } public void testReceiveSend_Empty_S2S() throws Exception { String msg = """"; DatagramPacket rdp = new DatagramPacket(msg.getBytes(), msg.length(), datagramSocket2Address); this.datagramSocket1.send(rdp); byte[] buf = new byte[CAPACITY_NORMAL]; this.datagramSocket2.setSoTimeout(TIME_UNIT); rdp = new DatagramPacket(buf, buf.length); this.datagramSocket2.receive(rdp); assertEquals(new String(buf, 0, CAPACITY_NORMAL).trim(), msg); } */ public void testReceiveSend_Block_Oversize() throws Exception { sendOnChannel2(""0123456789"", channel1Address); receiveOnChannel1AndClose(5, channel2Address, ""01234""); } public void testReceiveSend_Block_Oversize_C2S() throws Exception { sendOnChannel2(""0123456789"", datagramSocket1Address); receiveOnDatagramSocket1(5, ""01234""); } public void testReceiveSend_NonBlock_Oversize_C2S() throws Exception { this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); sendOnChannel2(""0123456789"", datagramSocket1Address); receiveOnDatagramSocket1(5, ""01234""); } public void testReceiveSend_Block_Oversize_S2C() throws Exception { sendOnDatagramSocket1(""0123456789"", channel1Address); receiveOnChannel1AndClose(5, datagramSocket1Address, ""01234""); } public void testReceiveSend_8K() throws Exception { StringBuffer str8k = new StringBuffer(); for (int i = 0; i < 8 * CAPACITY_1KB; i++) { str8k.append('a'); } String str = str8k.toString(); sendOnChannel2(str, channel1Address); receiveOnChannel1AndClose(8 * CAPACITY_1KB, channel2Address, str); } public void testReceiveSend_64K() throws Exception { StringBuffer str64k = new StringBuffer(); for (int i = 0; i < CAPACITY_64KB; i++) { str64k.append('a'); } String str = str64k.toString(); try { Thread.sleep(TIME_UNIT); channel2.send(ByteBuffer.wrap(str.getBytes()), datagramSocket1Address); fail(""Should throw SocketException!""); } catch (SocketException expected) { } } private void sendOnChannel2(String data, SocketAddress address) throws IOException { assertEquals(data.length(), channel2.send(ByteBuffer.wrap(data.getBytes()), address)); } private void sendOnDatagramSocket1(String data, InetSocketAddress address) throws Exception { DatagramPacket rdp = new DatagramPacket(data.getBytes(), data.length(), address); this.datagramSocket1.send(rdp); } private void receiveOnChannel1AndClose(int bufSize, InetSocketAddress expectedAddress, String expectedString) throws IOException { try { ByteBuffer buf = ByteBuffer.wrap(new byte[bufSize]); InetSocketAddress senderAddr; long startTime = System.currentTimeMillis(); do { senderAddr = (InetSocketAddress) this.channel1.receive(buf); // continue loop when channel1 is non-blocking and no data was // received. if (channel1.isBlocking() || senderAddr != null) { break; } // avoid dead loop assertTimeout(startTime, 10000); } while (true); assertEquals(senderAddr.getAddress(), Inet6Address.LOOPBACK); assertEquals(expectedAddress.getPort(), senderAddr.getPort()); assertEquals(new String(buf.array(), 0, buf.position()), expectedString); } finally { this.channel1.close(); } } /* * Fails if the difference between current time and start time is greater * than timeout. */ private void assertTimeout(long startTime, long timeout) { long currentTime = System.currentTimeMillis(); if ((currentTime - startTime) > timeout) { fail(""Timeout""); } } private void receiveOnDatagramSocket1(int bufSize, String expectedString) throws IOException { byte[] buf = new byte[bufSize]; this.datagramSocket1.setSoTimeout(6000); DatagramPacket rdp = new DatagramPacket(buf, buf.length); this.datagramSocket1.receive(rdp); assertEquals(new String(buf, 0, bufSize).trim(), expectedString); } public void testRead_fromSend() throws Exception { ByteBuffer buf = ByteBuffer.allocate(CAPACITY_NORMAL); String strHello = ""hello""; this.channel1.connect(channel2Address); this.channel2.send(ByteBuffer.wrap(strHello.getBytes()), channel1Address); assertEquals(strHello.length(), this.channel1.read(buf)); assertAscii(buf, strHello); } /* TODO(zgao): fix and enable. public void testReceive_Peek_NoSecurity_Nonblocking() throws Exception { String strHello = ""hello""; sendOnChannel2(strHello, channel1Address); this.channel1.configureBlocking(false); // for accepted addr, no problem. ByteBuffer buf = ByteBuffer.allocate(CAPACITY_NORMAL); InetSocketAddress source = (InetSocketAddress) this.channel1.receive(buf); assertEquals(channel2Address, source); assertAscii(buf, strHello); } */ private static void assertAscii(ByteBuffer b, String s) { assertEquals(s.length(), b.position()); for (int i = 0; i < s.length(); ++i) { assertEquals(s.charAt(i), b.get(i)); } } // ------------------------------------------------------------------- // Test for write() // ------------------------------------------------------------------- private void connectWriteBuf(InetSocketAddress ipAddr, ByteBuffer buf) throws IOException { this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); assertEquals(CAPACITY_NORMAL, this.channel1.write(buf)); assertEquals(0, this.channel1.write(buf)); } private void noconnectWrite(ByteBuffer buf) throws IOException { try { this.channel1.write(buf); fail(""should throw NotYetConnectedException""); } catch (NotYetConnectedException e) { // correct } } /* * Test method for 'DatagramChannelImpl.write(ByteBuffer)' */ public void testWriteByteBuffer_Block() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); connectWriteBuf(datagramSocket1Address, writeBuf); } public void testWriteByteBuffer_NonBlock() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); this.channel1.configureBlocking(false); connectWriteBuf(datagramSocket1Address, writeBuf); } public void testWriteByteBuffer_Block_closed() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; noconnectWrite(writeBuf); this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); this.channel1.close(); try { channel1.write(writeBuf); fail(""should throw ClosedChannelException""); } catch (ClosedChannelException e) { // correct } } public void testWriteByteBuffer_NonBlock_closed() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; // non block mode this.channel1.configureBlocking(false); noconnectWrite(writeBuf); this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); this.channel1.close(); try { channel1.write(writeBuf); fail(""should throw ClosedChannelException""); } catch (ClosedChannelException e) { // correct } } public void testWriteByteBuffer_Block_BufNull() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(0); InetSocketAddress ipAddr = datagramSocket1Address; try { this.channel1.write((ByteBuffer) null); fail(""Should throw NPE.""); } catch (NullPointerException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); try { this.channel1.write((ByteBuffer) null); fail(""Should throw NPE.""); } catch (NullPointerException e) { // correct } assertEquals(0, this.channel1.write(writeBuf)); datagramSocket1.close(); try { this.channel1.write((ByteBuffer) null); fail(""Should throw NPE.""); } catch (NullPointerException e) { // correct } } public void testWriteByteBuffer_NonBlock_BufNull() throws IOException { ByteBuffer writeBuf = ByteBuffer.allocateDirect(0); InetSocketAddress ipAddr = datagramSocket1Address; // non block mode this.channel1.configureBlocking(false); try { this.channel1.write((ByteBuffer) null); fail(""Should throw NPE.""); } catch (NullPointerException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); try { this.channel1.write((ByteBuffer) null); fail(""Should throw NPE.""); } catch (NullPointerException e) { // correct } assertEquals(0, this.channel1.write(writeBuf)); datagramSocket1.close(); try { this.channel1.write((ByteBuffer) null); fail(""Should throw NPE.""); } catch (NullPointerException e) { // correct } } /* * Test method for 'DatagramChannelImpl.write(ByteBuffer[], int, int)' */ public void testWriteByteBufferArrayIntInt_Block() throws IOException { ByteBuffer[] writeBuf = new ByteBuffer[2]; writeBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); writeBuf[1] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; try { this.channel1.write(writeBuf, 0, 2); fail(""Should throw NotYetConnectedException.""); } catch (NotYetConnectedException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); assertEquals(CAPACITY_NORMAL * 2, this.channel1.write(writeBuf, 0, 2)); // cannot be buffered again! assertEquals(0, this.channel1.write(writeBuf, 0, 1)); } public void testWriteByteBufferArrayIntInt_NonBlock() throws IOException { ByteBuffer[] writeBuf = new ByteBuffer[2]; writeBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); writeBuf[1] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; // non-block mode this.channel1.configureBlocking(false); try { this.channel1.write(writeBuf, 0, 2); fail(""Should throw NotYetConnectedException.""); } catch (NotYetConnectedException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); assertEquals(CAPACITY_NORMAL * 2, this.channel1.write(writeBuf, 0, 2)); // cannot be buffered again! assertEquals(0, this.channel1.write(writeBuf, 0, 1)); } public void testWriteByteBufferArrayIntInt_NoConnectIndexBad() throws IOException { ByteBuffer[] writeBuf = new ByteBuffer[2]; writeBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); writeBuf[1] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; try { this.channel1.write(writeBuf, -1, 2); fail(""should throw IndexOutOfBoundsException""); } catch (IndexOutOfBoundsException e) { // correct } try { this.channel1.write(writeBuf, 0, -1); fail(""should throw IndexOutOfBoundsException""); } catch (IndexOutOfBoundsException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); assertEquals(CAPACITY_NORMAL * 2, this.channel1.write(writeBuf, 0, 2)); // cannot be buffered again! assertEquals(0, this.channel1.write(writeBuf, 0, 1)); } public void testWriteByteBufferArrayIntInt_ConnectedIndexBad() throws IOException { ByteBuffer[] writeBuf = new ByteBuffer[2]; writeBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); writeBuf[1] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); try { this.channel1.write(writeBuf, -1, 2); fail(""should throw IndexOutOfBoundsException""); } catch (IndexOutOfBoundsException e) { // correct } try { this.channel1.write(writeBuf, 0, -1); fail(""should throw IndexOutOfBoundsException""); } catch (IndexOutOfBoundsException e) { // correct } } public void testWriteByteBufferArrayIntInt_BufNullNoConnect() throws IOException { ByteBuffer[] writeBuf = new ByteBuffer[2]; writeBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); try { this.channel1.write(null, 0, 2); fail(); } catch (NullPointerException expected) { } try { this.channel1.write(writeBuf, -1, 2); fail(); } catch (IndexOutOfBoundsException expected) { } try { this.channel1.write(writeBuf, 0, 3); fail(); } catch (IndexOutOfBoundsException expected) { } } public void testWriteByteBufferArrayIntInt_BufNullConnect() throws IOException { ByteBuffer[] writeBuf = new ByteBuffer[2]; writeBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); try { this.channel1.write(null, 0, 2); fail(""should throw NPE""); } catch (NullPointerException e) { // correct } try { this.channel1.write(writeBuf, 0, 3); fail(""should throw IndexOutOfBoundsException""); } catch (IndexOutOfBoundsException e) { // correct } datagramSocket1.close(); try { this.channel1.write(null, 0, 2); fail(""should throw NPE""); } catch (NullPointerException e) { // correct } } // ------------------------------------------------------------------- // Test for read() // ------------------------------------------------------------------- /* * Test method for 'DatagramChannelImpl.read(ByteBuffer)' */ public void testReadByteBuffer() throws IOException { ByteBuffer readBuf = ByteBuffer.allocateDirect(CAPACITY_NORMAL); try { this.channel1.read(readBuf); fail(""should throw NotYetConnectedException""); } catch (NotYetConnectedException e) { // correct } this.channel1.connect(datagramSocket1Address); assertTrue(this.channel1.isConnected()); this.channel1.configureBlocking(false); // note : blocking-mode will make the read process endless! assertEquals(0, this.channel1.read(readBuf)); this.channel1.close(); try { this.channel1.read(readBuf); fail(""Should throw ClosedChannelException""); } catch (ClosedChannelException e) { // OK. } } public void testReadByteBuffer_bufNull() throws IOException { ByteBuffer readBuf = ByteBuffer.allocateDirect(0); InetSocketAddress ipAddr = datagramSocket1Address; try { this.channel1.read(readBuf); fail(""should throw NotYetConnectedException""); } catch (NotYetConnectedException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); try { channel1.read((ByteBuffer) null); fail(""should throw NPE""); } catch (NullPointerException e) { // correct } this.channel1.configureBlocking(false); // note : blocking-mode will make the read process endless! assertEquals(0, this.channel1.read(readBuf)); datagramSocket1.close(); } /* * Test method for 'DatagramChannelImpl.read(ByteBuffer[], int, int)' */ public void testReadByteBufferArrayIntInt() throws IOException { ByteBuffer[] readBuf = new ByteBuffer[2]; readBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); readBuf[1] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; try { this.channel1.read(readBuf, 0, 2); fail(""should throw NotYetConnectedException""); } catch (NotYetConnectedException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); this.channel1.configureBlocking(false); // note : blocking-mode will make the read process endless! assertEquals(0, this.channel1.read(readBuf, 0, 1)); assertEquals(0, this.channel1.read(readBuf, 0, 2)); datagramSocket1.close(); } public void testReadByteBufferArrayIntInt_exceptions() throws IOException { //regression test for HARMONY-932 try { DatagramChannel.open().read(new ByteBuffer[] {}, 2, Integer.MAX_VALUE); fail(); } catch (IndexOutOfBoundsException expected) { } try { DatagramChannel.open().read(new ByteBuffer[] {}, -1, 0); fail(); } catch (IndexOutOfBoundsException expected) { } try { DatagramChannel.open().read((ByteBuffer[]) null, 0, 0); fail(); } catch (NullPointerException expected) { } } public void testReadByteBufferArrayIntInt_BufNull() throws IOException { ByteBuffer[] readBuf = new ByteBuffer[2]; readBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); InetSocketAddress ipAddr = datagramSocket1Address; try { this.channel1.read(null, 0, 0); fail(""should throw NPE""); } catch (NullPointerException e) { // correct } this.channel1.connect(ipAddr); assertTrue(this.channel1.isConnected()); this.channel1.configureBlocking(false); // note : blocking-mode will make the read process endless! try { this.channel1.read(null, 0, 0); fail(""should throw NPE""); } catch (NullPointerException e) { // correct } assertEquals(0, this.channel1.read(readBuf, 0, 1)); try { this.channel1.read(readBuf, 0, 2); fail(""should throw NPE""); } catch (NullPointerException e) { // correct } try { this.channel1.read(readBuf, 0, 3); fail(""should throw IndexOutOfBoundsException""); } catch (IndexOutOfBoundsException e) { // correct } datagramSocket1.close(); } // ------------------------------------------------------------------- // test read and write // ------------------------------------------------------------------- public static class A { protected int z; } public static class B extends A { protected int z; void foo() { super.z+=1; } } public void testReadWrite_asyncClose() throws Exception { byte[] targetArray = new byte[2]; ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); channel2.connect(channel1Address); channel1.connect(channel2Address); new Thread() { public void run() { try { Thread.sleep(TIME_UNIT); channel1.close(); } catch (Exception e) { //ignore } } }.start(); try { this.channel1.read(targetBuf); fail(""should throw AsynchronousCloseException""); } catch (AsynchronousCloseException e) { // ok } } public void testReadWrite_Block_Zero() throws Exception { byte[] sourceArray = new byte[0]; byte[] targetArray = new byte[0]; channel1.connect(channel2Address); channel2.connect(channel1Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(0, this.channel1.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); int readCount = this.channel2.read(targetBuf); assertEquals(0, readCount); } public void testReadWrite_Block_Normal() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } channel1.connect(channel2Address); channel2.connect(channel1Address); readWriteReadData(this.channel1, sourceArray, this.channel2, targetArray, CAPACITY_NORMAL, ""testReadWrite_Block_Normal""); } public void testReadWrite_Block_Empty() throws Exception { // empty buf byte[] sourceArray = """".getBytes(); byte[] targetArray = new byte[CAPACITY_NORMAL]; channel1.connect(channel2Address); channel2.connect(channel1Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(0, this.channel1.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); // empty message let the reader blocked closeBlockedReaderChannel2(targetBuf); } public void testReadWrite_changeBlock_Empty() throws Exception { // empty buf byte[] sourceArray = """".getBytes(); byte[] targetArray = new byte[CAPACITY_NORMAL]; channel1.connect(channel2Address); channel2.connect(channel1Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(0, this.channel1.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); // empty message let the reader blocked new Thread() { public void run() { try { Thread.sleep(TIME_UNIT); channel2.configureBlocking(false); Thread.sleep(TIME_UNIT * 5); channel2.close(); } catch (Exception e) { // do nothing } } }.start(); try { assertTrue(this.channel2.isBlocking()); this.channel2.read(targetBuf); fail(""Should throw AsynchronousCloseException""); } catch (AsynchronousCloseException e) { assertFalse(this.channel2.isBlocking()); // OK. } } public void testReadWrite_Block_8KB() throws Exception { byte[] sourceArray = new byte[CAPACITY_1KB * 8]; byte[] targetArray = new byte[CAPACITY_1KB * 8]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } channel1.connect(channel2Address); channel2.connect(channel1Address); readWriteReadData(this.channel1, sourceArray, this.channel2, targetArray, 8 * CAPACITY_1KB, ""testReadWrite_Block_8KB""); } /* * sender write the sourceArray whose size is dataSize, and receiver read * the data into targetArray */ private void readWriteReadData(DatagramChannel sender, byte[] sourceArray, DatagramChannel receiver, byte[] targetArray, int dataSize, String methodName) throws IOException { // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(dataSize, sender.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); int count = 0; int total = 0; long [MASK] = System.currentTimeMillis(); while (total < dataSize && (count = receiver.read(targetBuf)) != -1) { total = total + count; // 3s timeout to avoid dead loop if (System.currentTimeMillis() - [MASK] > 3000){ break; } } assertEquals(dataSize, total); assertEquals(targetBuf.position(), total); targetBuf.flip(); targetArray = targetBuf.array(); for (int i = 0; i < targetArray.length; i++) { assertEquals(targetArray[i], (byte) i); } } public void testReadWrite_Block_64K() throws Exception { byte[] sourceArray = new byte[CAPACITY_64KB]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } channel1.connect(channel2Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); try { channel1.write(sourceBuf); fail(""Should throw IOException""); } catch (IOException expected) { // too big } } public void testReadWrite_Block_DifferentAddr() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } this.channel1.connect(channel1.socket().getLocalSocketAddress()); this.channel2.connect(datagramSocket1Address); // the different addr // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(CAPACITY_NORMAL, this.channel1.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); // the wrong connected addr will make the read blocked. // we close the blocked channel closeBlockedReaderChannel2(targetBuf); } public void testReadWrite_Block_WriterNotBound() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } DatagramChannel dc = DatagramChannel.open(); // The writer isn't bound, but is connected. dc.connect(channel1Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(CAPACITY_NORMAL, dc.write(sourceBuf)); // Connect channel2 after data has been written. channel2.connect(dc.socket().getLocalSocketAddress()); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); closeBlockedReaderChannel2(targetBuf); dc.close(); } // NOTE: The original harmony test tested that things still work // if there's no socket bound at the the address we're connecting to. // // It isn't really feasible to implement that in a non-racy way. public void testReadWrite_Block_WriterConnectLater() throws Exception { byte[] targetArray = new byte[CAPACITY_NORMAL]; // The reader is bound & connected to channel1. channel2.connect(channel1Address); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); new Thread() { public void run() { try { Thread.sleep(TIME_UNIT); // bind later byte[] sourceArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } channel1.connect(channel2Address); // write later ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(CAPACITY_NORMAL, channel1.write(sourceBuf)); } catch (Exception e) { // do nothing } } }.start(); int count = 0; int total = 0; long [MASK] = System.currentTimeMillis(); while (total < CAPACITY_NORMAL && (count = channel2.read(targetBuf)) != -1) { total = total + count; // 3s timeout to avoid dead loop if (System.currentTimeMillis() - [MASK] > 3000){ break; } } assertEquals(CAPACITY_NORMAL, total); assertEquals(targetBuf.position(), total); targetBuf.flip(); targetArray = targetBuf.array(); for (int i = 0; i < targetArray.length; i++) { assertEquals(targetArray[i], (byte) i); } } // NOTE: The original harmony test tested that things still work // if there's no socket bound at the the address we're connecting to. // // It isn't really feasible to implement that in a non-racy way. public void testReadWrite_Block_ReaderNotConnected() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } // reader channel2 is not connected. this.channel1.connect(channel2Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(CAPACITY_NORMAL, this.channel1.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); try { this.channel2.read(targetBuf); fail(); } catch (NotYetConnectedException expected) { } } private void closeBlockedReaderChannel2(ByteBuffer targetBuf) throws IOException { assertTrue(this.channel2.isBlocking()); new Thread() { public void run() { try { Thread.sleep(TIME_UNIT); } catch (InterruptedException ie) { fail(); } IoUtils.closeQuietly(channel2); } }.start(); try { this.channel2.read(targetBuf); fail(""Should throw AsynchronousCloseException""); } catch (AsynchronousCloseException e) { // OK. } } // ------------------------------------------------------------------- // Test read and write in non-block mode. // ------------------------------------------------------------------- public void testReadWrite_NonBlock_Normal() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); channel1.connect(channel2Address); channel2.connect(channel1Address); readWriteReadData(this.channel1, sourceArray, this.channel2, targetArray, CAPACITY_NORMAL, ""testReadWrite_NonBlock_Normal""); } public void testReadWrite_NonBlock_8KB() throws Exception { byte[] sourceArray = new byte[CAPACITY_1KB * 8]; byte[] targetArray = new byte[CAPACITY_1KB * 8]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); // bind and connect channel1.connect(channel2Address); channel2.connect(channel1Address); readWriteReadData(this.channel1, sourceArray, this.channel2, targetArray, 8 * CAPACITY_1KB, ""testReadWrite_NonBlock_8KB""); } public void testReadWrite_NonBlock_DifferentAddr() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); channel1.connect(channel2Address); channel2.connect(datagramSocket1Address);// the different addr // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(CAPACITY_NORMAL, this.channel1.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); assertEquals(0, this.channel2.read(targetBuf)); } public void testReadWrite_NonBlock_WriterNotBound() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } DatagramChannel dc = DatagramChannel.open(); // The writer isn't bound, but is connected. dc.connect(channel1Address); dc.configureBlocking(false); channel2.configureBlocking(false); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(CAPACITY_NORMAL, dc.write(sourceBuf)); // Connect channel2 after data has been written. channel2.connect(dc.socket().getLocalSocketAddress()); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); assertEquals(0, this.channel2.read(targetBuf)); dc.close(); } // NOTE: The original harmony test tested that things still work // if there's no socket bound at the the address we're connecting to. // // It isn't really feasible to implement that in a non-racy way. public void testReadWrite_NonBlock_ReaderNotConnected() throws Exception { byte[] sourceArray = new byte[CAPACITY_NORMAL]; byte[] targetArray = new byte[CAPACITY_NORMAL]; for (int i = 0; i < sourceArray.length; i++) { sourceArray[i] = (byte) i; } this.channel1.configureBlocking(false); this.channel2.configureBlocking(false); channel1.connect(channel2Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); assertEquals(CAPACITY_NORMAL, this.channel1.write(sourceBuf)); // read ByteBuffer targetBuf = ByteBuffer.wrap(targetArray); try { assertEquals(0, this.channel2.read(targetBuf)); fail(); } catch (NotYetConnectedException expected) { } } public void test_write_LBuffer_positioned() throws Exception { // Regression test for Harmony-683 int position = 16; DatagramChannel dc = DatagramChannel.open(); byte[] sourceArray = new byte[CAPACITY_NORMAL]; dc.connect(datagramSocket1Address); // write ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); sourceBuf.position(position); assertEquals(CAPACITY_NORMAL - position, dc.write(sourceBuf)); } public void test_send_LBuffer_LSocketAddress_PositionNotZero() throws Exception { // regression test for Harmony-701 int CAPACITY_NORMAL = 256; int position = 16; DatagramChannel dc = DatagramChannel.open(); byte[] sourceArray = new byte[CAPACITY_NORMAL]; // send ByteBuffer whose position is not zero ByteBuffer sourceBuf = ByteBuffer.wrap(sourceArray); sourceBuf.position(position); int ret = dc.send(sourceBuf, datagramSocket1Address); // assert send (256 - 16) bytes assertEquals(CAPACITY_NORMAL - position, ret); // assert the position of ByteBuffer has been set assertEquals(CAPACITY_NORMAL, sourceBuf.position()); } /** * @tests DatagramChannel#read(ByteBuffer[]) */ public void test_read_$LByteBuffer() throws Exception { channel1.connect(channel2Address); channel2.connect(channel1Address); // regression test for Harmony-754 channel2.write(ByteBuffer.allocate(CAPACITY_NORMAL)); ByteBuffer[] readBuf = new ByteBuffer[2]; readBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); readBuf[1] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); channel1.configureBlocking(true); assertEquals(CAPACITY_NORMAL, channel1.read(readBuf)); } /** * @tests DatagramChannel#read(ByteBuffer[],int,int) */ public void test_read_$LByteBufferII() throws Exception { channel1.connect(channel2Address); channel2.connect(channel1Address); // regression test for Harmony-754 channel2.write(ByteBuffer.allocate(CAPACITY_NORMAL)); ByteBuffer[] readBuf = new ByteBuffer[2]; readBuf[0] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); readBuf[1] = ByteBuffer.allocateDirect(CAPACITY_NORMAL); channel1.configureBlocking(true); assertEquals(CAPACITY_NORMAL, channel1.read(readBuf, 0, 2)); } /** * @tests DatagramChannel#read(ByteBuffer) */ public void test_read_LByteBuffer_closed_nullBuf() throws Exception { // regression test for Harmony-754 ByteBuffer c = null; DatagramChannel channel = DatagramChannel.open(); channel.close(); try{ channel.read(c); fail(""Should throw NullPointerException""); } catch (NullPointerException e){ // expected } } /** * @tests DatagramChannel#read(ByteBuffer) */ public void test_read_LByteBuffer_NotConnected_nullBuf() throws Exception { // regression test for Harmony-754 ByteBuffer c = null; DatagramChannel channel = DatagramChannel.open(); try{ channel.read(c); fail(""Should throw NullPointerException""); } catch (NullPointerException e){ // expected } } /** * @tests DatagramChannel#read(ByteBuffer) */ public void test_read_LByteBuffer_readOnlyBuf() throws Exception { // regression test for Harmony-754 ByteBuffer c = ByteBuffer.allocate(1); DatagramChannel channel = DatagramChannel.open(); try{ channel.read(c.asReadOnlyBuffer()); fail(""Should throw NotYetConnectedException""); } catch (NotYetConnectedException e){ } catch (IllegalArgumentException e){ // expected } channel.connect(datagramSocket1Address); try{ channel.read(c.asReadOnlyBuffer()); fail(""Should throw IllegalArgumentException""); } catch (IllegalArgumentException e){ // expected } } /** * @tests DatagramChannel#send(ByteBuffer, SocketAddress) */ public void test_send_LByteBuffer_LSocketAddress_closed() throws IOException{ // regression test for Harmony-913 channel1.close(); ByteBuffer buf = ByteBuffer.allocate(CAPACITY_NORMAL); try { channel1.send(buf, datagramSocket1Address); fail(""Should throw ClosedChannelException""); } catch (ClosedChannelException e) { //pass } try { channel1.send(null, datagramSocket1Address); fail(""Should throw NullPointerException""); } catch (NullPointerException e) { //pass } try { channel1.send(buf, null); fail(""Should throw ClosedChannelException""); } catch (ClosedChannelException e) { //pass } try { channel1.send(null, null); fail(""Should throw NullPointerException""); } catch (NullPointerException e) { //pass } } /** * @tests DatagramChannel#socket() */ public void test_socket_IllegalBlockingModeException() throws Exception { // regression test for Harmony-1036 DatagramChannel channel = DatagramChannel.open(); channel.configureBlocking(false); DatagramSocket socket = channel.socket(); try { socket.send(null); fail(""should throw IllegalBlockingModeException""); } catch (IllegalBlockingModeException e) { // expected } try { socket.receive(null); fail(""should throw IllegalBlockingModeException""); } catch (IllegalBlockingModeException e) { // expected } channel.close(); } public void test_bounded_harmony6493() throws IOException { DatagramChannel server = DatagramChannel.open(); InetSocketAddress addr = new InetSocketAddress(""localhost"", 0); server.socket().bind(addr); SocketAddress boundedAddress = server.socket().getLocalSocketAddress(); DatagramChannel client = DatagramChannel.open(); ByteBuffer sent = ByteBuffer.allocate(1024); sent.put(""test"".getBytes()); sent.flip(); client.send(sent, boundedAddress); assertTrue(client.socket().isBound()); server.close(); client.close(); } public void test_bind_null() throws Exception { DatagramChannel dc = DatagramChannel.open(); try { assertNull(dc.socket().getLocalSocketAddress()); dc.socket().bind(null); InetSocketAddress localAddress = (InetSocketAddress) dc.socket().getLocalSocketAddress(); assertTrue(localAddress.getAddress().isAnyLocalAddress()); assertTrue(localAddress.getPort() > 0); } finally { dc.close(); } } public void test_bind_failure() throws Exception { DatagramChannel dc = DatagramChannel.open(); try { // Bind to a local address that is in use dc.socket().bind(channel1Address); fail(); } catch (IOException expected) { } finally { dc.close(); } } public void test_bind_closed() throws Exception { DatagramChannel dc = DatagramChannel.open(); dc.close(); try { dc.socket().bind(null); fail(); } catch (IOException expected) { } finally { dc.close(); } } /* J2ObjC: failed with msg ""bind failed: EADDRINUSE (Address already in use)"". public void test_bind_explicitPort() throws Exception { InetSocketAddress address = (InetSocketAddress) channel1.socket().getLocalSocketAddress(); assertTrue(address.getPort() > 0); DatagramChannel dc = DatagramChannel.open(); // Allow the socket to bind to a port we know is already in use. dc.socket().setReuseAddress(true); InetSocketAddress bindAddress = new InetSocketAddress(""localhost"", address.getPort()); dc.socket().bind(bindAddress); InetSocketAddress boundAddress = (InetSocketAddress) dc.socket().getLocalSocketAddress(); assertEquals(bindAddress.getHostName(), boundAddress.getHostName()); assertEquals(bindAddress.getPort(), boundAddress.getPort()); dc.close(); channel1.close(); } */ /** Checks that the SocketChannel and associated Socket agree on the socket state. */ public void test_bind_socketSync() throws IOException { DatagramChannel dc = DatagramChannel.open(); assertNull(dc.socket().getLocalSocketAddress()); DatagramSocket socket = dc.socket(); assertNull(socket.getLocalSocketAddress()); assertFalse(socket.isBound()); InetSocketAddress bindAddr = new InetSocketAddress(""localhost"", 0); dc.socket().bind(bindAddr); InetSocketAddress actualAddr = (InetSocketAddress) dc.socket().getLocalSocketAddress(); assertEquals(actualAddr, socket.getLocalSocketAddress()); assertEquals(bindAddr.getHostName(), actualAddr.getHostName()); assertTrue(socket.isBound()); assertFalse(socket.isConnected()); assertFalse(socket.isClosed()); dc.close(); assertFalse(dc.isOpen()); assertTrue(socket.isClosed()); } /** * Checks that the SocketChannel and associated Socket agree on the socket state, even if * the Socket object is requested/created after bind(). */ public void test_bind_socketSyncAfterBind() throws IOException { DatagramChannel dc = DatagramChannel.open(); assertNull(dc.socket().getLocalSocketAddress()); InetSocketAddress bindAddr = new InetSocketAddress(""localhost"", 0); dc.socket().bind(bindAddr); // Socket creation after bind(). DatagramSocket socket = dc.socket(); InetSocketAddress actualAddr = (InetSocketAddress) dc.socket().getLocalSocketAddress(); assertEquals(actualAddr, socket.getLocalSocketAddress()); assertEquals(bindAddr.getHostName(), actualAddr.getHostName()); assertTrue(socket.isBound()); assertFalse(socket.isConnected()); assertFalse(socket.isClosed()); dc.close(); assertFalse(dc.isOpen()); assertTrue(socket.isClosed()); } public void test_getLocalSocketAddress_afterClose() throws IOException { DatagramChannel dc = DatagramChannel.open(); assertNull(dc.socket().getLocalSocketAddress()); InetSocketAddress bindAddr = new InetSocketAddress(""localhost"", 0); dc.socket().bind(bindAddr); assertNotNull(dc.socket().getLocalSocketAddress()); dc.close(); assertFalse(dc.isOpen()); dc.socket().getLocalSocketAddress(); } // b/27294715 /* J2ObjC: fix and enable. public void test_concurrentShutdown() throws Exception { DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(true); dc.bind(new InetSocketAddress(Inet6Address.LOOPBACK, 0)); // Set 4s timeout dc.socket().setSoTimeout(4000); final AtomicReference killerThreadException = new AtomicReference(null); final Thread killer = new Thread(new Runnable() { public void run() { try { Thread.sleep(2000); try { Libcore.os.shutdown(dc.socket().getFileDescriptor$(), OsConstants.SHUT_RDWR); } catch (ErrnoException expected) { if (OsConstants.ENOTCONN != expected.errno) { killerThreadException.set(expected); } } } catch (Exception ex) { killerThreadException.set(ex); } } }); killer.start(); ByteBuffer dst = ByteBuffer.allocate(CAPACITY_NORMAL); assertEquals(null, dc.receive(dst)); assertEquals(0, dst.position()); dc.close(); killer.join(); assertNull(killerThreadException.get()); } */ } ","beginTime " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.classfile.editor; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.annotation.visitor.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; /** * This ClassVisitor fixes references of constant pool entries, fields, * methods, and attributes to classes whose names have changed. Descriptors * of member references are not updated yet. * * @see MemberReferenceFixer * @author Eric Lafortune */ public class ClassReferenceFixer extends SimplifiedVisitor implements ClassVisitor, ConstantVisitor, MemberVisitor, AttributeVisitor, InnerClassesInfoVisitor, LocalVariableInfoVisitor, LocalVariableTypeInfoVisitor, AnnotationVisitor, ElementValueVisitor { private final boolean ensureUniqueMemberNames; /** * Creates a new ClassReferenceFixer. * @param ensureUniqueMemberNames specifies whether class members whose * descriptor changes should get new, unique * names, in order to avoid naming conflicts * with similar methods. */ public ClassReferenceFixer(boolean ensureUniqueMemberNames) { this.ensureUniqueMemberNames = ensureUniqueMemberNames; } // Implementations for ClassVisitor. public void visitProgramClass(ProgramClass programClass) { // Fix the constant pool. programClass.constantPoolEntriesAccept(this); // Fix class members. programClass.fieldsAccept(this); programClass.methodsAccept(this); // Fix the attributes. programClass.attributesAccept(this); } public void visitLibraryClass(LibraryClass libraryClass) { // Fix class members. libraryClass.fieldsAccept(this); libraryClass.methodsAccept(this); } // Implementations for MemberVisitor. public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Has the descriptor changed? String descriptor = programField.getDescriptor(programClass); String newDescriptor = newDescriptor(descriptor, programField.referencedClass); if (!descriptor.equals(newDescriptor)) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the descriptor. programField.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Update the name, if requested. if (ensureUniqueMemberNames) { String name = programField.getName(programClass); String newName = newUniqueMemberName(name, descriptor); programField.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); } } // Fix the attributes. programField.attributesAccept(programClass, this); } public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { // Has the descriptor changed? String descriptor = programMethod.getDescriptor(programClass); String newDescriptor = newDescriptor(descriptor, programMethod.referencedClasses); if (!descriptor.equals(newDescriptor)) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass); // Update the descriptor. programMethod.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor); // Update the name, if requested. if (ensureUniqueMemberNames) { String name = programMethod.getName(programClass); String newName = newUniqueMemberName(name, descriptor); programMethod.u2nameIndex = constantPoolEditor.addUtf8Constant(newName); } } // Fix the attributes. programMethod.attributesAccept(programClass, this); } public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) { // Has the descriptor changed? String descriptor = libraryField.getDescriptor(libraryClass); String newDescriptor = newDescriptor(descriptor, libraryField.referencedClass); // Update the descriptor. libraryField.descriptor = newDescriptor; } public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) { // Has the descriptor changed? String descriptor = libraryMethod.getDescriptor(libraryClass); String newDescriptor = newDescriptor(descriptor, libraryMethod.referencedClasses); if (!descriptor.equals(newDescriptor)) { // Update the descriptor. libraryMethod.descriptor = newDescriptor; } } // Implementations for ConstantVisitor. public void visitAnyConstant(Clazz clazz, Constant constant) {} public void visitStringConstant(Clazz clazz, StringConstant stringConstant) { // Does the string refer to a class, due to a Class.forName construct? Clazz referencedClass = stringConstant.referencedClass; Member referencedMember = stringConstant.referencedMember; if (referencedClass != null && referencedMember == null) { // Reconstruct the new class name. String externalClassName = stringConstant.getString(clazz); String internalClassName = ClassUtil.internalClassName(externalClassName); String newInternalClassName = newClassName(internalClassName, referencedClass); // Update the String entry if required. if (!newInternalClassName.equals(internalClassName)) { // Only convert to an external class name if the original was // an external class name too. String newExternalClassName = externalClassName.indexOf(JavaConstants.PACKAGE_SEPARATOR) >= 0 ? ClassUtil.externalClassName(newInternalClassName) : newInternalClassName; // Refer to a new Utf8 entry. stringConstant.u2stringIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newExternalClassName); } } } public void visitDynamicConstant(Clazz clazz, DynamicConstant dynamicConstant) { // Has the descriptor changed? String descriptor = dynamicConstant.getType(clazz); String newDescriptor = newDescriptor(descriptor, dynamicConstant.referencedClasses); if (!descriptor.equals(newDescriptor)) { String name = dynamicConstant.getName(clazz); // Refer to a new NameAndType entry. dynamicConstant.u2nameAndTypeIndex = new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(name, newDescriptor); } } public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { // Has the descriptor changed? String descriptor = invokeDynamicConstant.getType(clazz); String newDescriptor = newDescriptor(descriptor, invokeDynamicConstant.referencedClasses); if (!descriptor.equals(newDescriptor)) { String name = invokeDynamicConstant.getName(clazz); // Refer to a new NameAndType entry. invokeDynamicConstant.u2nameAndTypeIndex = new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(name, newDescriptor); } } public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { // Do we know the referenced class? Clazz referencedClass = classConstant.referencedClass; if (referencedClass != null) { // Has the class name changed? String className = classConstant.getName(clazz); String newClassName = newClassName(className, referencedClass); if (!className.equals(newClassName)) { // Refer to a new Utf8 entry. classConstant.u2nameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName); } } } public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) { // Has the descriptor changed? String descriptor = methodTypeConstant.getType(clazz); String newDescriptor = newDescriptor(descriptor, methodTypeConstant.referencedClasses); if (!descriptor.equals(newDescriptor)) { // Update the descriptor. methodTypeConstant.u2descriptorIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor); } } // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { // Fix the inner class names. innerClassesAttribute.innerClassEntriesAccept(clazz, this); } public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // Fix the attributes. codeAttribute.attributesAccept(clazz, method, this); } public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) { // Fix the types of the local variables. localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) { // Fix the signatures of the local variables. localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); } public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute) { // Has the signature changed? String signature = signatureAttribute.getSignature(clazz); String newSignature = newDescriptor(signature, signatureAttribute.referencedClasses); if (!signature.equals(newSignature)) { // Update the signature. signatureAttribute.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); } } public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) { // Fix the annotations. annotationsAttribute.annotationsAccept(clazz, this); } public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) { // Fix the annotations. parameterAnnotationsAttribute.annotationsAccept(clazz, method, this); } public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) { // Fix the annotation. annotationDefaultAttribute.defaultValueAccept(clazz, this); } // Implementations for InnerClassesInfoVisitor. public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { // Fix the inner class name. int innerClassIndex = innerClassesInfo.u2innerClassIndex; int innerNameIndex = innerClassesInfo.u2innerNameIndex; if (innerClassIndex != 0 && innerNameIndex != 0) { String newInnerName = clazz.getClassName(innerClassIndex); int index = newInnerName.lastIndexOf(ClassConstants.INNER_CLASS_SEPARATOR); if (index >= 0) { innerClassesInfo.u2innerNameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newInnerName.substring(index + 1)); } } } // Implementations for LocalVariableInfoVisitor. public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) { // Has the descriptor changed? String descriptor = localVariableInfo.getDescriptor(clazz); String newDescriptor = newDescriptor(descriptor, localVariableInfo.referencedClass); if (!descriptor.equals(newDescriptor)) { // Refer to a new Utf8 entry. localVariableInfo.u2descriptorIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor); } } // Implementations for LocalVariableTypeInfoVisitor. public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) { // Has the signature changed? String signature = localVariableTypeInfo.getSignature(clazz); String newSignature = newDescriptor(signature, localVariableTypeInfo.referencedClasses); if (!signature.equals(newSignature)) { // Update the signature. localVariableTypeInfo.u2signatureIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature); } } // Implementations for AnnotationVisitor. public void visitAnnotation(Clazz clazz, Annotation annotation) { // Has the type changed? String typeName = annotation.getType(clazz); String [MASK] = newDescriptor(typeName, annotation.referencedClasses); if (!typeName.equals( [MASK] )) { // Update the type. annotation.u2typeIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant( [MASK] ); } // Fix the element values. annotation.elementValuesAccept(clazz, this); } // Implementations for ElementValueVisitor. public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) { } public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) { // Has the type name chamged? String typeName = enumConstantElementValue.getTypeName(clazz); String [MASK] = newDescriptor(typeName, enumConstantElementValue.referencedClasses); if (!typeName.equals( [MASK] )) { // Update the type name. enumConstantElementValue.u2typeNameIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant( [MASK] ); } } public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) { // Has the class info changed? String className = classElementValue.getClassName(clazz); String newClassName = newDescriptor(className, classElementValue.referencedClasses); if (!className.equals(newClassName)) { // Update the class info. classElementValue.u2classInfoIndex = new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName); } } public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) { // Fix the annotation. annotationElementValue.annotationAccept(clazz, this); } public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) { // Fix the element values. arrayElementValue.elementValuesAccept(clazz, annotation, this); } // Small utility methods. private static String newDescriptor(String descriptor, Clazz referencedClass) { // If there is no referenced class, the descriptor won't change. if (referencedClass == null) { return descriptor; } // Unravel and reconstruct the class element of the descriptor. DescriptorClassEnumeration descriptorClassEnumeration = new DescriptorClassEnumeration(descriptor); StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff()); // Only if the descriptor contains a class name (e.g. with an array of // primitive types), the descriptor can change. if (descriptorClassEnumeration.hasMoreClassNames()) { String className = descriptorClassEnumeration.nextClassName(); String fluff = descriptorClassEnumeration.nextFluff(); String newClassName = newClassName(className, referencedClass); newDescriptorBuffer.append(newClassName); newDescriptorBuffer.append(fluff); } return newDescriptorBuffer.toString(); } private static String newDescriptor(String descriptor, Clazz[] referencedClasses) { // If there are no referenced classes, the descriptor won't change. if (referencedClasses == null || referencedClasses.length == 0) { return descriptor; } // Unravel and reconstruct the class elements of the descriptor. DescriptorClassEnumeration descriptorClassEnumeration = new DescriptorClassEnumeration(descriptor); StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length()); newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff()); int index = 0; while (descriptorClassEnumeration.hasMoreClassNames()) { String className = descriptorClassEnumeration.nextClassName(); boolean isInnerClassName = descriptorClassEnumeration.isInnerClassName(); String fluff = descriptorClassEnumeration.nextFluff(); String newClassName = newClassName(className, referencedClasses[index++]); // Strip the outer class name again, if it's an inner class. if (isInnerClassName) { newClassName = newClassName.substring(newClassName.lastIndexOf(ClassConstants.INNER_CLASS_SEPARATOR)+1); } newDescriptorBuffer.append(newClassName); newDescriptorBuffer.append(fluff); } return newDescriptorBuffer.toString(); } /** * Returns a unique class member name, based on the given name and descriptor. */ private String newUniqueMemberName(String name, String descriptor) { return name.equals(ClassConstants.METHOD_NAME_INIT) ? ClassConstants.METHOD_NAME_INIT : name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode())); } /** * Returns the new class name based on the given class name and the new * name of the given referenced class. Class names of array types * are handled properly. */ private static String newClassName(String className, Clazz referencedClass) { // If there is no referenced class, the class name won't change. if (referencedClass == null) { return className; } // Reconstruct the class name. String newClassName = referencedClass.getName(); // Is it an array type? if (className.charAt(0) == ClassConstants.TYPE_ARRAY) { // Add the array prefixes and suffix ""[L...;"". newClassName = className.substring(0, className.indexOf(ClassConstants.TYPE_CLASS_START)+1) + newClassName + ClassConstants.TYPE_CLASS_END; } return newClassName; } } ","newTypeName " "/* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the ""License""); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an ""AS IS"" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.dexpatcher.util; import com.tencent.tinker.android.dex.ClassData; import com.tencent.tinker.android.dex.ClassDef; import com.tencent.tinker.android.dex.Code; import com.tencent.tinker.android.dex.Dex; import com.tencent.tinker.android.dex.FieldId; import com.tencent.tinker.android.dex.MethodId; import com.tencent.tinker.android.dex.ProtoId; import com.tencent.tinker.android.dx.instruction.InstructionCodec; import com.tencent.tinker.android.dx.instruction.InstructionReader; import com.tencent.tinker.android.dx.instruction.InstructionVisitor; import com.tencent.tinker.android.dx.instruction.ShortArrayCodeInput; import com.tencent.tinker.build.util.DexClassesComparator; import com.tencent.tinker.commons.dexpatcher.DexPatcherLogger; import java.io.EOFException; import java.util.Collection; import java.util.HashSet; import java.util.Set; import static com.tencent.tinker.build.util.DexClassesComparator.DexClassInfo; import static com.tencent.tinker.build.util.DexClassesComparator.DexGroup; /** * Created by tangyinsheng on 2017/2/26. */ public class ChangedClassesDexClassInfoCollector { private static final String TAG = ""ChangedClassesDexClassInfoCollector""; private static final DexPatcherLogger LOGGER = new DexPatcherLogger(); private final Set excludedClassPatterns = new HashSet<>(); private boolean includeRefererToRefererAffectedClasses = false; public ChangedClassesDexClassInfoCollector setExcludedClassPatterns(Collection loaderClassPatterns) { this.excludedClassPatterns.clear(); this.excludedClassPatterns.addAll(loaderClassPatterns); return this; } public ChangedClassesDexClassInfoCollector clearExcludedClassPatterns() { this.excludedClassPatterns.clear(); return this; } public ChangedClassesDexClassInfoCollector setLogger(DexPatcherLogger.IDexPatcherLogger loggerImpl) { LOGGER.setLoggerImpl(loggerImpl); return this; } public ChangedClassesDexClassInfoCollector setIncludeRefererToRefererAffectedClasses(boolean enabled) { this.includeRefererToRefererAffectedClasses = enabled; return this; } public Set doCollect(DexGroup oldDexGroup, DexGroup newDexGroup) { final Set classDescsInResult = new HashSet<>(); final Set result = new HashSet<>(); DexClassesComparator dexClassCmptor = new DexClassesComparator(""*""); dexClassCmptor.setCompareMode(DexClassesComparator.COMPARE_MODE_NORMAL); dexClassCmptor.setIgnoredRemovedClassDescPattern(excludedClassPatterns); dexClassCmptor.setLogger(LOGGER.getLoggerImpl()); dexClassCmptor.startCheck(oldDexGroup, newDexGroup); // So far we collected infos of all added, changed, and deleted classes. result.addAll(dexClassCmptor.getAddedClassInfos()); final Collection changedClassInfos = dexClassCmptor.getChangedClassDescToInfosMap().values(); for (DexClassInfo[] oldAndNewInfoPair : changedClassInfos) { final DexClassInfo newClassInfo = oldAndNewInfoPair[1]; LOGGER.i(TAG, ""Add class %s to changed classes dex."", newClassInfo.classDesc); result.add(newClassInfo); } for (DexClassInfo classInfo : result) { classDescsInResult.add(classInfo.classDesc); } if (includeRefererToRefererAffectedClasses) { // Then we also need to add classes who refer to classes with referrer // affected changes to the result. (referrer affected change means the changes // that may cause referrer refer to wrong target.) dexClassCmptor.setCompareMode(DexClassesComparator.COMPARE_MODE_REFERRER_AFFECTED_CHANGE_ONLY); dexClassCmptor.startCheck(oldDexGroup, newDexGroup); Set referrerAffectedChangedClassDescs = dexClassCmptor.getChangedClassDescToInfosMap().keySet(); Set oldClassInfos = oldDexGroup.getClassInfosInDexesWithDuplicateCheck(); for (DexClassInfo oldClassInfo : oldClassInfos) { if (!classDescsInResult.contains(oldClassInfo.classDesc) && isClassReferToAnyClasses(oldClassInfo, referrerAffectedChangedClassDescs)) { LOGGER.i(TAG, ""Add class %s in old dex to changed classes dex since it is affected by modified referee."", oldClassInfo.classDesc); result.add(oldClassInfo); } } } return result; } private boolean isClassReferToAnyClasses(DexClassInfo classInfo, Set refereeClassDescs) { if (classInfo.classDef.classDataOffset == ClassDef.NO_OFFSET) { return false; } ClassData classData = classInfo.owner.readClassData(classInfo.classDef); for (ClassData.Method method : classData.directMethods) { if (isMethodReferToAnyClasses(classInfo, method, refereeClassDescs)) { return true; } } for (ClassData.Method method : classData.virtualMethods) { if (isMethodReferToAnyClasses(classInfo, method, refereeClassDescs)) { return true; } } return false; } private boolean isMethodReferToAnyClasses(DexClassInfo classInfo, ClassData.Method method, Set refereeClassDescs) { if (method.codeOffset == ClassDef.NO_OFFSET) { return false; } Code methodCode = classInfo.owner.readCode(method); InstructionReader ir = new InstructionReader(new ShortArrayCodeInput(methodCode.instructions)); ReferToClassesCheckVisitor rtcv = new ReferToClassesCheckVisitor(classInfo.owner, method, refereeClassDescs); try { ir.accept(rtcv); } catch (EOFException e) { // Should not be here. } return rtcv.isReferToAnyRefereeClasses; } private static class ReferToClassesCheckVisitor extends InstructionVisitor { private final Dex owner; private final ClassData.Method method; private final Collection refereeClassDescs; private boolean isReferToAnyRefereeClasses = false; ReferToClassesCheckVisitor(Dex owner, ClassData.Method method, Collection refereeClassDescs) { super(null); this.owner = owner; this.method = method; this.refereeClassDescs = refereeClassDescs; } @Override public void visitZeroRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal) { processIndexByType(index, indexType); } @Override public void visitOneRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a) { processIndexByType(index, indexType); } @Override public void visitTwoRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b) { processIndexByType(index, indexType); } @Override public void visitThreeRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c) { processIndexByType(index, indexType); } @Override public void visitFourRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d) { processIndexByType(index, indexType); } @Override public void visitFiveRegisterInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int b, int c, int d, int e) { processIndexByType(index, indexType); } @Override public void visitRegisterRangeInsn(int currentAddress, int opcode, int index, int indexType, int target, long literal, int a, int registerCount) { processIndexByType(index, indexType); } private void processIndexByType(int index, int indexType) { String typeName = null; String [MASK] = null; switch (indexType) { case InstructionCodec.INDEX_TYPE_TYPE_REF: { typeName = owner.typeNames().get(index); [MASK] = ""init referrer-affected class""; break; } case InstructionCodec.INDEX_TYPE_FIELD_REF: { final FieldId fieldId = owner.fieldIds().get(index); typeName = owner.typeNames().get(fieldId.declaringClassIndex); [MASK] = ""referencing to field: "" + owner.strings().get(fieldId.nameIndex); break; } case InstructionCodec.INDEX_TYPE_METHOD_REF: { final MethodId methodId = owner.methodIds().get(index); typeName = owner.typeNames().get(methodId.declaringClassIndex); [MASK] = ""invoking method: "" + getMethodProtoTypeStr(methodId); break; } default: { break; } } if (typeName != null && refereeClassDescs.contains(typeName)) { MethodId methodId = owner.methodIds().get(method.methodIndex); LOGGER.i( TAG, ""Method %s in class %s referenced referrer-affected class %s by %s"", getMethodProtoTypeStr(methodId), owner.typeNames().get(methodId.declaringClassIndex), typeName, [MASK] ); isReferToAnyRefereeClasses = true; } } private String getMethodProtoTypeStr(MethodId methodId) { StringBuilder strBuilder = new StringBuilder(); strBuilder.append(owner.strings().get(methodId.nameIndex)); ProtoId protoId = owner.protoIds().get(methodId.protoIndex); strBuilder.append('('); short[] paramTypeIds = owner.parameterTypeIndicesFromMethodId(methodId); for (short typeId : paramTypeIds) { strBuilder.append(owner.typeNames().get(typeId)); } strBuilder.append(')').append(owner.typeNames().get(protoId.returnTypeIndex)); return strBuilder.toString(); } } } ","refInfoInLog " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 2004-2010, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * */ package android.icu.dev.test.timescale; import java.util.Date; import java.util.Locale; import org.junit.Test; import android.icu.dev.test.TestFmwk; import android.icu.util.GregorianCalendar; import android.icu.util.SimpleTimeZone; import android.icu.util.TimeZone; import android.icu.util.UniversalTimeScale; /** * @author Owner * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Style - Code Templates */ public class TimeScaleDataTest extends TestFmwk { /** * Default contstructor. */ public TimeScaleDataTest() { } private void roundTripTest(long value, int scale) { long rt = UniversalTimeScale.toLong(UniversalTimeScale.from(value, scale), scale); if (rt != value) { errln(""Round-trip error: time scale = "" + scale + "", value = "" + value + "", round-trip = "" + rt); } } private void toLimitTest(long toLimit, long fromLimit, int scale) { long result = UniversalTimeScale.toLong(toLimit, scale); if (result != fromLimit) { errln(""toLimit failure: scale = "" + scale + "", toLimit = "" + toLimit + "", toLong(toLimit, scale) = "" + result + "", fromLimit = "" + fromLimit); } } private void epochOffsetTest(long epochOffset, long units, int scale) { long universalEpoch = epochOffset * units; long local = UniversalTimeScale.toLong(universalEpoch, scale); if (local != 0) { errln(""toLong(epochOffset, scale): scale = "" + scale + "", epochOffset = "" + universalEpoch + "", result = "" + local); } local = UniversalTimeScale.toLong(0, scale); if (local != -epochOffset) { errln(""toLong(0, scale): scale = "" + scale + "", result = "" + local); } long universal = UniversalTimeScale.from(-epochOffset, scale); if (universal != 0) { errln(""from(-epochOffest, scale): scale = "" + scale + "", epochOffset = "" + epochOffset + "", result = "" + universal); } universal = UniversalTimeScale.from(0, scale); if (universal != universalEpoch) { errln(""from(0, scale): scale = "" + scale + "", result = "" + universal); } } @Test public void TestEpochOffsets() { for (int scale = 0; scale < UniversalTimeScale.MAX_SCALE; scale += 1) { long units = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.UNITS_VALUE); long epochOffset = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.EPOCH_OFFSET_VALUE); epochOffsetTest(epochOffset, units, scale); } } @Test public void TestFromLimits() { for (int scale = 0; scale < UniversalTimeScale.MAX_SCALE; scale += 1) { long fromMin = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.FROM_MIN_VALUE); long fromMax = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.FROM_MAX_VALUE); roundTripTest(fromMin, scale); roundTripTest(fromMax, scale); } } @Test public void TestToLimits() { for (int scale = 0; scale < UniversalTimeScale.MAX_SCALE; scale += 1) { long fromMin = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.FROM_MIN_VALUE); long fromMax = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.FROM_MAX_VALUE); long [MASK] = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.TO_MIN_VALUE); long toMax = UniversalTimeScale.getTimeScaleValue(scale, UniversalTimeScale.TO_MAX_VALUE); toLimitTest( [MASK] , fromMin, scale); toLimitTest(toMax, fromMax, scale); } } // Test with data from .Net System.DateTime ---------------------------- *** /* * This data was generated by C++.Net code like * Console::WriteLine(L"" {{ {0}, 1, 1, INT64_C({1}) }},"", year, DateTime(year, 1, 1).Ticks); * with the DateTime constructor taking int values for year, month, and date. */ static private final long dotNetDateTimeTicks[] = { /* year, month, day, ticks */ 100, 1, 1, 31241376000000000L, 100, 3, 1, 31292352000000000L, 200, 1, 1, 62798112000000000L, 200, 3, 1, 62849088000000000L, 300, 1, 1, 94354848000000000L, 300, 3, 1, 94405824000000000L, 400, 1, 1, 125911584000000000L, 400, 3, 1, 125963424000000000L, 500, 1, 1, 157469184000000000L, 500, 3, 1, 157520160000000000L, 600, 1, 1, 189025920000000000L, 600, 3, 1, 189076896000000000L, 700, 1, 1, 220582656000000000L, 700, 3, 1, 220633632000000000L, 800, 1, 1, 252139392000000000L, 800, 3, 1, 252191232000000000L, 900, 1, 1, 283696992000000000L, 900, 3, 1, 283747968000000000L, 1000, 1, 1, 315253728000000000L, 1000, 3, 1, 315304704000000000L, 1100, 1, 1, 346810464000000000L, 1100, 3, 1, 346861440000000000L, 1200, 1, 1, 378367200000000000L, 1200, 3, 1, 378419040000000000L, 1300, 1, 1, 409924800000000000L, 1300, 3, 1, 409975776000000000L, 1400, 1, 1, 441481536000000000L, 1400, 3, 1, 441532512000000000L, 1500, 1, 1, 473038272000000000L, 1500, 3, 1, 473089248000000000L, 1600, 1, 1, 504595008000000000L, 1600, 3, 1, 504646848000000000L, 1700, 1, 1, 536152608000000000L, 1700, 3, 1, 536203584000000000L, 1800, 1, 1, 567709344000000000L, 1800, 3, 1, 567760320000000000L, 1900, 1, 1, 599266080000000000L, 1900, 3, 1, 599317056000000000L, 2000, 1, 1, 630822816000000000L, 2000, 3, 1, 630874656000000000L, 2100, 1, 1, 662380416000000000L, 2100, 3, 1, 662431392000000000L, 2200, 1, 1, 693937152000000000L, 2200, 3, 1, 693988128000000000L, 2300, 1, 1, 725493888000000000L, 2300, 3, 1, 725544864000000000L, 2400, 1, 1, 757050624000000000L, 2400, 3, 1, 757102464000000000L, 2500, 1, 1, 788608224000000000L, 2500, 3, 1, 788659200000000000L, 2600, 1, 1, 820164960000000000L, 2600, 3, 1, 820215936000000000L, 2700, 1, 1, 851721696000000000L, 2700, 3, 1, 851772672000000000L, 2800, 1, 1, 883278432000000000L, 2800, 3, 1, 883330272000000000L, 2900, 1, 1, 914836032000000000L, 2900, 3, 1, 914887008000000000L, 3000, 1, 1, 946392768000000000L, 3000, 3, 1, 946443744000000000L, 1, 1, 1, 0L, 1601, 1, 1, 504911232000000000L, 1899, 12, 31, 599265216000000000L, 1904, 1, 1, 600527520000000000L, 1970, 1, 1, 621355968000000000L, 2001, 1, 1, 631139040000000000L, 9900, 3, 1, 3123873216000000000L, 9999, 12, 31, 3155378112000000000L }; /* * ICU's Universal Time Scale is designed to be tick-for-tick compatible with * .Net System.DateTime. Verify that this is so for the * .Net-supported date range (years 1-9999 AD). * This requires a proleptic Gregorian calendar because that's what .Net uses. * Proleptic: No Julian/Gregorian switchover, or a switchover before * any date that we test, that is, before 0001 AD. */ @Test public void TestDotNet() { TimeZone utc; final long dayMillis = 86400 * 1000L; /* 1 day = 86400 seconds */ final long dayTicks = 86400 * 10000000L; final int kYear = 0; // offset for dotNetDateTimeTicks[] field final int kMonth = 1; final int kDay = 2; final int kTicks = 3; final int kIncrement = 4; GregorianCalendar cal; long icuDate; long ticks, millis; int i; /* Open a proleptic Gregorian calendar. */ long before0001AD = -1000000 * dayMillis; utc = new SimpleTimeZone(0, ""UTC""); cal = new GregorianCalendar(utc, Locale.ENGLISH); cal.setGregorianChange(new Date(before0001AD)); for(i = 0; i < dotNetDateTimeTicks.length; i += kIncrement) { /* Test conversion from .Net/Universal time to ICU time. */ millis = UniversalTimeScale.toLong(dotNetDateTimeTicks[i + kTicks], UniversalTimeScale.ICU4C_TIME); cal.clear(); cal.set((int)dotNetDateTimeTicks[i + kYear], (int)dotNetDateTimeTicks[i + kMonth] - 1, /* Java & ICU use January = month 0. */ (int)dotNetDateTimeTicks[i + kDay]); icuDate = cal.getTimeInMillis(); if(millis != icuDate) { /* Print days not millis. */ errln(""UniversalTimeScale.toLong(ticks["" + i + ""], ICU4C)="" + (millis/dayMillis) + "" != "" + (icuDate/dayMillis) + ""=ucal_getMillis("" + dotNetDateTimeTicks[i + kYear] + ""-"" + dotNetDateTimeTicks[i + kMonth] + ""-"" + dotNetDateTimeTicks[i + kDay] + "")""); } /* Test conversion from ICU time to .Net/Universal time. */ ticks = UniversalTimeScale.from(icuDate, UniversalTimeScale.ICU4C_TIME); if(ticks != dotNetDateTimeTicks[i + kTicks]) { /* Print days not ticks. */ errln(""UniversalTimeScale.from(date["" + i + ""], ICU4C)="" + (ticks/dayTicks) + "" != "" + dotNetDateTimeTicks[i + kTicks]/dayTicks + ""=.Net System.DateTime("" + dotNetDateTimeTicks[i + kYear] + ""-"" + dotNetDateTimeTicks[i + kMonth] + ""-"" + dotNetDateTimeTicks[i + kDay] + "").Ticks""); } } } } ","toMin " "/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2; import static androidx.annotation.VisibleForTesting.PROTECTED; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.msToUs; import static com.google.android.exoplayer2.util.Util.usToMs; import static java.lang.Math.max; import static java.lang.Math.min; import android.graphics.Rect; import android.os.Looper; import android.os.SystemClock; import android.util.Pair; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import androidx.annotation.FloatRange; import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Size; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.ForOverride; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * A base implementation for {@link Player} that reduces the number of methods to implement to a * minimum. * *

Implementation notes: * *

    *
  • Subclasses must override {@link #getState()} to populate the current player state on * request. *
  • The {@link State} should set the {@linkplain State.Builder#setAvailableCommands available * commands} to indicate which {@link Player} methods are supported. *
  • All setter-like player methods (for example, {@link #setPlayWhenReady}) forward to * overridable methods (for example, {@link #handleSetPlayWhenReady}) that can be used to * handle these requests. These methods return a {@link ListenableFuture} to indicate when the * request has been handled and is fully reflected in the values returned from {@link * #getState}. This class will automatically request a state update once the request is done. * If the state changes can be handled synchronously, these methods can return Guava's {@link * Futures#immediateVoidFuture()}. *
  • Subclasses can manually trigger state updates with {@link #invalidateState}, for example if * something changes independent of {@link Player} method calls. *
* * This base class handles various aspects of the player implementation to simplify the subclass: * *
    *
  • The {@link State} can only be created with allowed combinations of state values, avoiding * any invalid player states. *
  • Only functionality that is declared as {@linkplain Player.Command available} needs to be * implemented. Other methods are automatically ignored. *
  • Listener handling and informing listeners of state changes is handled automatically. *
  • The base class provides a framework for asynchronous handling of method calls. It changes * the visible playback state immediately to the most likely outcome to ensure the * user-visible state changes look like synchronous operations. The state is then updated * again once the asynchronous method calls have been fully handled. *
* * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public abstract class SimpleBasePlayer extends BasePlayer { /** An immutable state description of the player. */ protected static final class State { /** A builder for {@link State} objects. */ public static final class Builder { private Commands availableCommands; private boolean playWhenReady; private @PlayWhenReadyChangeReason int playWhenReadyChangeReason; private @Player.State int playbackState; private @PlaybackSuppressionReason int playbackSuppressionReason; @Nullable private PlaybackException playerError; private @RepeatMode int repeatMode; private boolean shuffleModeEnabled; private boolean isLoading; private long seekBackIncrementMs; private long seekForwardIncrementMs; private long maxSeekToPreviousPositionMs; private PlaybackParameters playbackParameters; private TrackSelectionParameters trackSelectionParameters; private AudioAttributes audioAttributes; private float volume; private VideoSize videoSize; private CueGroup currentCues; private DeviceInfo deviceInfo; private int deviceVolume; private boolean isDeviceMuted; private Size surfaceSize; private boolean newlyRenderedFirstFrame; private Metadata timedMetadata; private ImmutableList playlist; private Timeline timeline; private MediaMetadata playlistMetadata; private int currentMediaItemIndex; private int currentAdGroupIndex; private int currentAdIndexInAdGroup; @Nullable private Long contentPositionMs; private PositionSupplier contentPositionMsSupplier; @Nullable private Long adPositionMs; private PositionSupplier adPositionMsSupplier; private PositionSupplier contentBufferedPositionMsSupplier; private PositionSupplier [MASK] ; private PositionSupplier totalBufferedDurationMsSupplier; private boolean hasPositionDiscontinuity; private @Player.DiscontinuityReason int positionDiscontinuityReason; private long discontinuityPositionMs; /** Creates the builder. */ public Builder() { availableCommands = Commands.EMPTY; playWhenReady = false; playWhenReadyChangeReason = Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; playbackState = Player.STATE_IDLE; playbackSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE; playerError = null; repeatMode = Player.REPEAT_MODE_OFF; shuffleModeEnabled = false; isLoading = false; seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS; seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS; maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; playbackParameters = PlaybackParameters.DEFAULT; trackSelectionParameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT; audioAttributes = AudioAttributes.DEFAULT; volume = 1f; videoSize = VideoSize.UNKNOWN; currentCues = CueGroup.EMPTY_TIME_ZERO; deviceInfo = DeviceInfo.UNKNOWN; deviceVolume = 0; isDeviceMuted = false; surfaceSize = Size.UNKNOWN; newlyRenderedFirstFrame = false; timedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET); playlist = ImmutableList.of(); timeline = Timeline.EMPTY; playlistMetadata = MediaMetadata.EMPTY; currentMediaItemIndex = C.INDEX_UNSET; currentAdGroupIndex = C.INDEX_UNSET; currentAdIndexInAdGroup = C.INDEX_UNSET; contentPositionMs = null; contentPositionMsSupplier = PositionSupplier.getConstant(C.TIME_UNSET); adPositionMs = null; adPositionMsSupplier = PositionSupplier.ZERO; contentBufferedPositionMsSupplier = PositionSupplier.getConstant(C.TIME_UNSET); [MASK] = PositionSupplier.ZERO; totalBufferedDurationMsSupplier = PositionSupplier.ZERO; hasPositionDiscontinuity = false; positionDiscontinuityReason = Player.DISCONTINUITY_REASON_INTERNAL; discontinuityPositionMs = 0; } private Builder(State state) { this.availableCommands = state.availableCommands; this.playWhenReady = state.playWhenReady; this.playWhenReadyChangeReason = state.playWhenReadyChangeReason; this.playbackState = state.playbackState; this.playbackSuppressionReason = state.playbackSuppressionReason; this.playerError = state.playerError; this.repeatMode = state.repeatMode; this.shuffleModeEnabled = state.shuffleModeEnabled; this.isLoading = state.isLoading; this.seekBackIncrementMs = state.seekBackIncrementMs; this.seekForwardIncrementMs = state.seekForwardIncrementMs; this.maxSeekToPreviousPositionMs = state.maxSeekToPreviousPositionMs; this.playbackParameters = state.playbackParameters; this.trackSelectionParameters = state.trackSelectionParameters; this.audioAttributes = state.audioAttributes; this.volume = state.volume; this.videoSize = state.videoSize; this.currentCues = state.currentCues; this.deviceInfo = state.deviceInfo; this.deviceVolume = state.deviceVolume; this.isDeviceMuted = state.isDeviceMuted; this.surfaceSize = state.surfaceSize; this.newlyRenderedFirstFrame = state.newlyRenderedFirstFrame; this.timedMetadata = state.timedMetadata; this.playlist = state.playlist; this.timeline = state.timeline; this.playlistMetadata = state.playlistMetadata; this.currentMediaItemIndex = state.currentMediaItemIndex; this.currentAdGroupIndex = state.currentAdGroupIndex; this.currentAdIndexInAdGroup = state.currentAdIndexInAdGroup; this.contentPositionMs = null; this.contentPositionMsSupplier = state.contentPositionMsSupplier; this.adPositionMs = null; this.adPositionMsSupplier = state.adPositionMsSupplier; this.contentBufferedPositionMsSupplier = state.contentBufferedPositionMsSupplier; this. [MASK] = state. [MASK] ; this.totalBufferedDurationMsSupplier = state.totalBufferedDurationMsSupplier; this.hasPositionDiscontinuity = state.hasPositionDiscontinuity; this.positionDiscontinuityReason = state.positionDiscontinuityReason; this.discontinuityPositionMs = state.discontinuityPositionMs; } /** * Sets the available {@link Commands}. * * @param availableCommands The available {@link Commands}. * @return This builder. */ @CanIgnoreReturnValue public Builder setAvailableCommands(Commands availableCommands) { this.availableCommands = availableCommands; return this; } /** * Sets whether playback should proceed when ready and not suppressed. * * @param playWhenReady Whether playback should proceed when ready and not suppressed. * @param playWhenReadyChangeReason The {@linkplain PlayWhenReadyChangeReason reason} for * changing the value. * @return This builder. */ @CanIgnoreReturnValue public Builder setPlayWhenReady( boolean playWhenReady, @PlayWhenReadyChangeReason int playWhenReadyChangeReason) { this.playWhenReady = playWhenReady; this.playWhenReadyChangeReason = playWhenReadyChangeReason; return this; } /** * Sets the {@linkplain Player.State state} of the player. * *

If the {@linkplain #setPlaylist playlist} is empty, the state must be either {@link * Player#STATE_IDLE} or {@link Player#STATE_ENDED}. * * @param playbackState The {@linkplain Player.State state} of the player. * @return This builder. */ @CanIgnoreReturnValue public Builder setPlaybackState(@Player.State int playbackState) { this.playbackState = playbackState; return this; } /** * Sets the reason why playback is suppressed even if {@link #getPlayWhenReady()} is true. * * @param playbackSuppressionReason The {@link Player.PlaybackSuppressionReason} why playback * is suppressed even if {@link #getPlayWhenReady()} is true. * @return This builder. */ @CanIgnoreReturnValue public Builder setPlaybackSuppressionReason( @Player.PlaybackSuppressionReason int playbackSuppressionReason) { this.playbackSuppressionReason = playbackSuppressionReason; return this; } /** * Sets last error that caused playback to fail, or null if there was no error. * *

The {@linkplain #setPlaybackState playback state} must be set to {@link * Player#STATE_IDLE} while an error is set. * * @param playerError The last error that caused playback to fail, or null if there was no * error. * @return This builder. */ @CanIgnoreReturnValue public Builder setPlayerError(@Nullable PlaybackException playerError) { this.playerError = playerError; return this; } /** * Sets the {@link RepeatMode} used for playback. * * @param repeatMode The {@link RepeatMode} used for playback. * @return This builder. */ @CanIgnoreReturnValue public Builder setRepeatMode(@Player.RepeatMode int repeatMode) { this.repeatMode = repeatMode; return this; } /** * Sets whether shuffling of media items is enabled. * * @param shuffleModeEnabled Whether shuffling of media items is enabled. * @return This builder. */ @CanIgnoreReturnValue public Builder setShuffleModeEnabled(boolean shuffleModeEnabled) { this.shuffleModeEnabled = shuffleModeEnabled; return this; } /** * Sets whether the player is currently loading its source. * *

The player can not be marked as loading if the {@linkplain #setPlaybackState state} is * {@link Player#STATE_IDLE} or {@link Player#STATE_ENDED}. * * @param isLoading Whether the player is currently loading its source. * @return This builder. */ @CanIgnoreReturnValue public Builder setIsLoading(boolean isLoading) { this.isLoading = isLoading; return this; } /** * Sets the {@link Player#seekBack()} increment in milliseconds. * * @param seekBackIncrementMs The {@link Player#seekBack()} increment in milliseconds. * @return This builder. */ @CanIgnoreReturnValue public Builder setSeekBackIncrementMs(long seekBackIncrementMs) { this.seekBackIncrementMs = seekBackIncrementMs; return this; } /** * Sets the {@link Player#seekForward()} increment in milliseconds. * * @param seekForwardIncrementMs The {@link Player#seekForward()} increment in milliseconds. * @return This builder. */ @CanIgnoreReturnValue public Builder setSeekForwardIncrementMs(long seekForwardIncrementMs) { this.seekForwardIncrementMs = seekForwardIncrementMs; return this; } /** * Sets the maximum position for which {@link #seekToPrevious()} seeks to the previous item, * in milliseconds. * * @param maxSeekToPreviousPositionMs The maximum position for which {@link #seekToPrevious()} * seeks to the previous item, in milliseconds. * @return This builder. */ @CanIgnoreReturnValue public Builder setMaxSeekToPreviousPositionMs(long maxSeekToPreviousPositionMs) { this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs; return this; } /** * Sets the currently active {@link PlaybackParameters}. * * @param playbackParameters The currently active {@link PlaybackParameters}. * @return This builder. */ @CanIgnoreReturnValue public Builder setPlaybackParameters(PlaybackParameters playbackParameters) { this.playbackParameters = playbackParameters; return this; } /** * Sets the currently active {@link TrackSelectionParameters}. * * @param trackSelectionParameters The currently active {@link TrackSelectionParameters}. * @return This builder. */ @CanIgnoreReturnValue public Builder setTrackSelectionParameters( TrackSelectionParameters trackSelectionParameters) { this.trackSelectionParameters = trackSelectionParameters; return this; } /** * Sets the current {@link AudioAttributes}. * * @param audioAttributes The current {@link AudioAttributes}. * @return This builder. */ @CanIgnoreReturnValue public Builder setAudioAttributes(AudioAttributes audioAttributes) { this.audioAttributes = audioAttributes; return this; } /** * Sets the current audio volume, with 0 being silence and 1 being unity gain (signal * unchanged). * * @param volume The current audio volume, with 0 being silence and 1 being unity gain (signal * unchanged). * @return This builder. */ @CanIgnoreReturnValue public Builder setVolume(@FloatRange(from = 0, to = 1.0) float volume) { checkArgument(volume >= 0.0f && volume <= 1.0f); this.volume = volume; return this; } /** * Sets the current video size. * * @param videoSize The current video size. * @return This builder. */ @CanIgnoreReturnValue public Builder setVideoSize(VideoSize videoSize) { this.videoSize = videoSize; return this; } /** * Sets the current {@linkplain CueGroup cues}. * * @param currentCues The current {@linkplain CueGroup cues}. * @return This builder. */ @CanIgnoreReturnValue public Builder setCurrentCues(CueGroup currentCues) { this.currentCues = currentCues; return this; } /** * Sets the {@link DeviceInfo}. * * @param deviceInfo The {@link DeviceInfo}. * @return This builder. */ @CanIgnoreReturnValue public Builder setDeviceInfo(DeviceInfo deviceInfo) { this.deviceInfo = deviceInfo; return this; } /** * Sets the current device volume. * * @param deviceVolume The current device volume. * @return This builder. */ @CanIgnoreReturnValue public Builder setDeviceVolume(@IntRange(from = 0) int deviceVolume) { checkArgument(deviceVolume >= 0); this.deviceVolume = deviceVolume; return this; } /** * Sets whether the device is muted. * * @param isDeviceMuted Whether the device is muted. * @return This builder. */ @CanIgnoreReturnValue public Builder setIsDeviceMuted(boolean isDeviceMuted) { this.isDeviceMuted = isDeviceMuted; return this; } /** * Sets the size of the surface onto which the video is being rendered. * * @param surfaceSize The surface size. Dimensions may be {@link C#LENGTH_UNSET} if unknown, * or 0 if the video is not rendered onto a surface. * @return This builder. */ @CanIgnoreReturnValue public Builder setSurfaceSize(Size surfaceSize) { this.surfaceSize = surfaceSize; return this; } /** * Sets whether a frame has been rendered for the first time since setting the surface, a * rendering reset, or since the stream being rendered was changed. * *

Note: As this will trigger a {@link Listener#onRenderedFirstFrame()} event, the flag * should only be set for the first {@link State} update after the first frame was rendered. * * @param newlyRenderedFirstFrame Whether the first frame was newly rendered. * @return This builder. */ @CanIgnoreReturnValue public Builder setNewlyRenderedFirstFrame(boolean newlyRenderedFirstFrame) { this.newlyRenderedFirstFrame = newlyRenderedFirstFrame; return this; } /** * Sets the most recent timed {@link Metadata}. * *

Metadata with a {@link Metadata#presentationTimeUs} of {@link C#TIME_UNSET} will not be * forwarded to listeners. * * @param timedMetadata The most recent timed {@link Metadata}. * @return This builder. */ @CanIgnoreReturnValue public Builder setTimedMetadata(Metadata timedMetadata) { this.timedMetadata = timedMetadata; return this; } /** * Sets the list of {@link MediaItemData media items} in the playlist. * *

All items must have unique {@linkplain MediaItemData.Builder#setUid UIDs}. * * @param playlist The list of {@link MediaItemData media items} in the playlist. * @return This builder. */ @CanIgnoreReturnValue public Builder setPlaylist(List playlist) { HashSet uids = new HashSet<>(); for (int i = 0; i < playlist.size(); i++) { checkArgument(uids.add(playlist.get(i).uid), ""Duplicate MediaItemData UID in playlist""); } this.playlist = ImmutableList.copyOf(playlist); this.timeline = new PlaylistTimeline(this.playlist); return this; } /** * Sets the playlist {@link MediaMetadata}. * * @param playlistMetadata The playlist {@link MediaMetadata}. * @return This builder. */ @CanIgnoreReturnValue public Builder setPlaylistMetadata(MediaMetadata playlistMetadata) { this.playlistMetadata = playlistMetadata; return this; } /** * Sets the current media item index. * *

The media item index must be less than the number of {@linkplain #setPlaylist media * items in the playlist}, if set. * * @param currentMediaItemIndex The current media item index, or {@link C#INDEX_UNSET} to * assume the default first item in the playlist. * @return This builder. */ @CanIgnoreReturnValue public Builder setCurrentMediaItemIndex(int currentMediaItemIndex) { this.currentMediaItemIndex = currentMediaItemIndex; return this; } /** * Sets the current ad indices, or {@link C#INDEX_UNSET} if no ad is playing. * *

Either both indices need to be {@link C#INDEX_UNSET} or both are not {@link * C#INDEX_UNSET}. * *

Ads indices can only be set if there is a corresponding {@link AdPlaybackState} defined * in the current {@linkplain MediaItemData.Builder#setPeriods period}. * * @param adGroupIndex The current ad group index, or {@link C#INDEX_UNSET} if no ad is * playing. * @param adIndexInAdGroup The current ad index in the ad group, or {@link C#INDEX_UNSET} if * no ad is playing. * @return This builder. */ @CanIgnoreReturnValue public Builder setCurrentAd(int adGroupIndex, int adIndexInAdGroup) { checkArgument((adGroupIndex == C.INDEX_UNSET) == (adIndexInAdGroup == C.INDEX_UNSET)); this.currentAdGroupIndex = adGroupIndex; this.currentAdIndexInAdGroup = adIndexInAdGroup; return this; } /** * Sets the current content playback position in milliseconds. * *

This position will be converted to an advancing {@link PositionSupplier} if the overall * state indicates an advancing playback position. * *

This method overrides any other {@link PositionSupplier} set via {@link * #setContentPositionMs(PositionSupplier)}. * * @param positionMs The current content playback position in milliseconds, or {@link * C#TIME_UNSET} to indicate the default start position. * @return This builder. */ @CanIgnoreReturnValue public Builder setContentPositionMs(long positionMs) { this.contentPositionMs = positionMs; return this; } /** * Sets the {@link PositionSupplier} for the current content playback position in * milliseconds. * *

The supplier is expected to return the updated position on every call if the playback is * advancing, for example by using {@link PositionSupplier#getExtrapolating}. * *

This method overrides any other position set via {@link #setContentPositionMs(long)}. * * @param contentPositionMsSupplier The {@link PositionSupplier} for the current content * playback position in milliseconds, or {@link C#TIME_UNSET} to indicate the default * start position. * @return This builder. */ @CanIgnoreReturnValue public Builder setContentPositionMs(PositionSupplier contentPositionMsSupplier) { this.contentPositionMs = null; this.contentPositionMsSupplier = contentPositionMsSupplier; return this; } /** * Sets the current ad playback position in milliseconds. The value is unused if no ad is * playing. * *

This position will be converted to an advancing {@link PositionSupplier} if the overall * state indicates an advancing ad playback position. * *

This method overrides any other {@link PositionSupplier} set via {@link * #setAdPositionMs(PositionSupplier)}. * * @param positionMs The current ad playback position in milliseconds. * @return This builder. */ @CanIgnoreReturnValue public Builder setAdPositionMs(long positionMs) { this.adPositionMs = positionMs; return this; } /** * Sets the {@link PositionSupplier} for the current ad playback position in milliseconds. The * value is unused if no ad is playing. * *

The supplier is expected to return the updated position on every call if the playback is * advancing, for example by using {@link PositionSupplier#getExtrapolating}. * *

This method overrides any other position set via {@link #setAdPositionMs(long)}. * * @param adPositionMsSupplier The {@link PositionSupplier} for the current ad playback * position in milliseconds. The value is unused if no ad is playing. * @return This builder. */ @CanIgnoreReturnValue public Builder setAdPositionMs(PositionSupplier adPositionMsSupplier) { this.adPositionMs = null; this.adPositionMsSupplier = adPositionMsSupplier; return this; } /** * Sets the {@link PositionSupplier} for the estimated position up to which the currently * playing content is buffered, in milliseconds. * * @param contentBufferedPositionMsSupplier The {@link PositionSupplier} for the estimated * position up to which the currently playing content is buffered, in milliseconds, or * {@link C#TIME_UNSET} to indicate the default start position. * @return This builder. */ @CanIgnoreReturnValue public Builder setContentBufferedPositionMs( PositionSupplier contentBufferedPositionMsSupplier) { this.contentBufferedPositionMsSupplier = contentBufferedPositionMsSupplier; return this; } /** * Sets the {@link PositionSupplier} for the estimated position up to which the currently * playing ad is buffered, in milliseconds. The value is unused if no ad is playing. * * @param [MASK] The {@link PositionSupplier} for the estimated position * up to which the currently playing ad is buffered, in milliseconds. The value is unused * if no ad is playing. * @return This builder. */ @CanIgnoreReturnValue public Builder setAdBufferedPositionMs(PositionSupplier [MASK] ) { this. [MASK] = [MASK] ; return this; } /** * Sets the {@link PositionSupplier} for the estimated total buffered duration in * milliseconds. * * @param totalBufferedDurationMsSupplier The {@link PositionSupplier} for the estimated total * buffered duration in milliseconds. * @return This builder. */ @CanIgnoreReturnValue public Builder setTotalBufferedDurationMs(PositionSupplier totalBufferedDurationMsSupplier) { this.totalBufferedDurationMsSupplier = totalBufferedDurationMsSupplier; return this; } /** * Signals that a position discontinuity happened since the last player update and sets the * reason for it. * * @param positionDiscontinuityReason The {@linkplain Player.DiscontinuityReason reason} for * the discontinuity. * @param discontinuityPositionMs The position, in milliseconds, in the current content or ad * from which playback continues after the discontinuity. * @return This builder. * @see #clearPositionDiscontinuity */ @CanIgnoreReturnValue public Builder setPositionDiscontinuity( @Player.DiscontinuityReason int positionDiscontinuityReason, long discontinuityPositionMs) { this.hasPositionDiscontinuity = true; this.positionDiscontinuityReason = positionDiscontinuityReason; this.discontinuityPositionMs = discontinuityPositionMs; return this; } /** * Clears a previously set position discontinuity signal. * * @return This builder. * @see #hasPositionDiscontinuity */ @CanIgnoreReturnValue public Builder clearPositionDiscontinuity() { this.hasPositionDiscontinuity = false; return this; } /** Builds the {@link State}. */ public State build() { return new State(this); } } /** The available {@link Commands}. */ public final Commands availableCommands; /** Whether playback should proceed when ready and not suppressed. */ public final boolean playWhenReady; /** The last reason for changing {@link #playWhenReady}. */ public final @PlayWhenReadyChangeReason int playWhenReadyChangeReason; /** The {@linkplain Player.State state} of the player. */ public final @Player.State int playbackState; /** The reason why playback is suppressed even if {@link #getPlayWhenReady()} is true. */ public final @PlaybackSuppressionReason int playbackSuppressionReason; /** The last error that caused playback to fail, or null if there was no error. */ @Nullable public final PlaybackException playerError; /** The {@link RepeatMode} used for playback. */ public final @RepeatMode int repeatMode; /** Whether shuffling of media items is enabled. */ public final boolean shuffleModeEnabled; /** Whether the player is currently loading its source. */ public final boolean isLoading; /** The {@link Player#seekBack()} increment in milliseconds. */ public final long seekBackIncrementMs; /** The {@link Player#seekForward()} increment in milliseconds. */ public final long seekForwardIncrementMs; /** * The maximum position for which {@link #seekToPrevious()} seeks to the previous item, in * milliseconds. */ public final long maxSeekToPreviousPositionMs; /** The currently active {@link PlaybackParameters}. */ public final PlaybackParameters playbackParameters; /** The currently active {@link TrackSelectionParameters}. */ public final TrackSelectionParameters trackSelectionParameters; /** The current {@link AudioAttributes}. */ public final AudioAttributes audioAttributes; /** The current audio volume, with 0 being silence and 1 being unity gain (signal unchanged). */ @FloatRange(from = 0, to = 1.0) public final float volume; /** The current video size. */ public final VideoSize videoSize; /** The current {@linkplain CueGroup cues}. */ public final CueGroup currentCues; /** The {@link DeviceInfo}. */ public final DeviceInfo deviceInfo; /** The current device volume. */ @IntRange(from = 0) public final int deviceVolume; /** Whether the device is muted. */ public final boolean isDeviceMuted; /** The size of the surface onto which the video is being rendered. */ public final Size surfaceSize; /** * Whether a frame has been rendered for the first time since setting the surface, a rendering * reset, or since the stream being rendered was changed. */ public final boolean newlyRenderedFirstFrame; /** The most recent timed metadata. */ public final Metadata timedMetadata; /** The media items in the playlist. */ public final ImmutableList playlist; /** The {@link Timeline} derived from the {@link #playlist}. */ public final Timeline timeline; /** The playlist {@link MediaMetadata}. */ public final MediaMetadata playlistMetadata; /** * The current media item index, or {@link C#INDEX_UNSET} to assume the default first item of * the playlist is played. */ public final int currentMediaItemIndex; /** The current ad group index, or {@link C#INDEX_UNSET} if no ad is playing. */ public final int currentAdGroupIndex; /** The current ad index in the ad group, or {@link C#INDEX_UNSET} if no ad is playing. */ public final int currentAdIndexInAdGroup; /** * The {@link PositionSupplier} for the current content playback position in milliseconds, or * {@link C#TIME_UNSET} to indicate the default start position. */ public final PositionSupplier contentPositionMsSupplier; /** * The {@link PositionSupplier} for the current ad playback position in milliseconds. The value * is unused if no ad is playing. */ public final PositionSupplier adPositionMsSupplier; /** * The {@link PositionSupplier} for the estimated position up to which the currently playing * content is buffered, in milliseconds, or {@link C#TIME_UNSET} to indicate the default start * position. */ public final PositionSupplier contentBufferedPositionMsSupplier; /** * The {@link PositionSupplier} for the estimated position up to which the currently playing ad * is buffered, in milliseconds. The value is unused if no ad is playing. */ public final PositionSupplier [MASK] ; /** The {@link PositionSupplier} for the estimated total buffered duration in milliseconds. */ public final PositionSupplier totalBufferedDurationMsSupplier; /** Signals that a position discontinuity happened since the last update to the player. */ public final boolean hasPositionDiscontinuity; /** * The {@linkplain Player.DiscontinuityReason reason} for the last position discontinuity. The * value is unused if {@link #hasPositionDiscontinuity} is {@code false}. */ public final @Player.DiscontinuityReason int positionDiscontinuityReason; /** * The position, in milliseconds, in the current content or ad from which playback continued * after the discontinuity. The value is unused if {@link #hasPositionDiscontinuity} is {@code * false}. */ public final long discontinuityPositionMs; private State(Builder builder) { if (builder.timeline.isEmpty()) { checkArgument( builder.playbackState == Player.STATE_IDLE || builder.playbackState == Player.STATE_ENDED, ""Empty playlist only allowed in STATE_IDLE or STATE_ENDED""); checkArgument( builder.currentAdGroupIndex == C.INDEX_UNSET && builder.currentAdIndexInAdGroup == C.INDEX_UNSET, ""Ads not allowed if playlist is empty""); } else { int mediaItemIndex = builder.currentMediaItemIndex; if (mediaItemIndex == C.INDEX_UNSET) { mediaItemIndex = 0; // TODO: Use shuffle order to find first index. } else { checkArgument( builder.currentMediaItemIndex < builder.timeline.getWindowCount(), ""currentMediaItemIndex must be less than playlist.size()""); } if (builder.currentAdGroupIndex != C.INDEX_UNSET) { Timeline.Period period = new Timeline.Period(); Timeline.Window window = new Timeline.Window(); long contentPositionMs = builder.contentPositionMs != null ? builder.contentPositionMs : builder.contentPositionMsSupplier.get(); int periodIndex = getPeriodIndexFromWindowPosition( builder.timeline, mediaItemIndex, contentPositionMs, window, period); builder.timeline.getPeriod(periodIndex, period); checkArgument( builder.currentAdGroupIndex < period.getAdGroupCount(), ""PeriodData has less ad groups than adGroupIndex""); int adCountInGroup = period.getAdCountInAdGroup(builder.currentAdGroupIndex); if (adCountInGroup != C.LENGTH_UNSET) { checkArgument( builder.currentAdIndexInAdGroup < adCountInGroup, ""Ad group has less ads than adIndexInGroupIndex""); } } } if (builder.playerError != null) { checkArgument( builder.playbackState == Player.STATE_IDLE, ""Player error only allowed in STATE_IDLE""); } if (builder.playbackState == Player.STATE_IDLE || builder.playbackState == Player.STATE_ENDED) { checkArgument( !builder.isLoading, ""isLoading only allowed when not in STATE_IDLE or STATE_ENDED""); } PositionSupplier contentPositionMsSupplier = builder.contentPositionMsSupplier; if (builder.contentPositionMs != null) { if (builder.currentAdGroupIndex == C.INDEX_UNSET && builder.playWhenReady && builder.playbackState == Player.STATE_READY && builder.playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE && builder.contentPositionMs != C.TIME_UNSET) { contentPositionMsSupplier = PositionSupplier.getExtrapolating( builder.contentPositionMs, builder.playbackParameters.speed); } else { contentPositionMsSupplier = PositionSupplier.getConstant(builder.contentPositionMs); } } PositionSupplier adPositionMsSupplier = builder.adPositionMsSupplier; if (builder.adPositionMs != null) { if (builder.currentAdGroupIndex != C.INDEX_UNSET && builder.playWhenReady && builder.playbackState == Player.STATE_READY && builder.playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE) { adPositionMsSupplier = PositionSupplier.getExtrapolating(builder.adPositionMs, /* playbackSpeed= */ 1f); } else { adPositionMsSupplier = PositionSupplier.getConstant(builder.adPositionMs); } } this.availableCommands = builder.availableCommands; this.playWhenReady = builder.playWhenReady; this.playWhenReadyChangeReason = builder.playWhenReadyChangeReason; this.playbackState = builder.playbackState; this.playbackSuppressionReason = builder.playbackSuppressionReason; this.playerError = builder.playerError; this.repeatMode = builder.repeatMode; this.shuffleModeEnabled = builder.shuffleModeEnabled; this.isLoading = builder.isLoading; this.seekBackIncrementMs = builder.seekBackIncrementMs; this.seekForwardIncrementMs = builder.seekForwardIncrementMs; this.maxSeekToPreviousPositionMs = builder.maxSeekToPreviousPositionMs; this.playbackParameters = builder.playbackParameters; this.trackSelectionParameters = builder.trackSelectionParameters; this.audioAttributes = builder.audioAttributes; this.volume = builder.volume; this.videoSize = builder.videoSize; this.currentCues = builder.currentCues; this.deviceInfo = builder.deviceInfo; this.deviceVolume = builder.deviceVolume; this.isDeviceMuted = builder.isDeviceMuted; this.surfaceSize = builder.surfaceSize; this.newlyRenderedFirstFrame = builder.newlyRenderedFirstFrame; this.timedMetadata = builder.timedMetadata; this.playlist = builder.playlist; this.timeline = builder.timeline; this.playlistMetadata = builder.playlistMetadata; this.currentMediaItemIndex = builder.currentMediaItemIndex; this.currentAdGroupIndex = builder.currentAdGroupIndex; this.currentAdIndexInAdGroup = builder.currentAdIndexInAdGroup; this.contentPositionMsSupplier = contentPositionMsSupplier; this.adPositionMsSupplier = adPositionMsSupplier; this.contentBufferedPositionMsSupplier = builder.contentBufferedPositionMsSupplier; this. [MASK] = builder. [MASK] ; this.totalBufferedDurationMsSupplier = builder.totalBufferedDurationMsSupplier; this.hasPositionDiscontinuity = builder.hasPositionDiscontinuity; this.positionDiscontinuityReason = builder.positionDiscontinuityReason; this.discontinuityPositionMs = builder.discontinuityPositionMs; } /** Returns a {@link Builder} pre-populated with the current state values. */ public Builder buildUpon() { return new Builder(this); } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (!(o instanceof State)) { return false; } State state = (State) o; return playWhenReady == state.playWhenReady && playWhenReadyChangeReason == state.playWhenReadyChangeReason && availableCommands.equals(state.availableCommands) && playbackState == state.playbackState && playbackSuppressionReason == state.playbackSuppressionReason && Util.areEqual(playerError, state.playerError) && repeatMode == state.repeatMode && shuffleModeEnabled == state.shuffleModeEnabled && isLoading == state.isLoading && seekBackIncrementMs == state.seekBackIncrementMs && seekForwardIncrementMs == state.seekForwardIncrementMs && maxSeekToPreviousPositionMs == state.maxSeekToPreviousPositionMs && playbackParameters.equals(state.playbackParameters) && trackSelectionParameters.equals(state.trackSelectionParameters) && audioAttributes.equals(state.audioAttributes) && volume == state.volume && videoSize.equals(state.videoSize) && currentCues.equals(state.currentCues) && deviceInfo.equals(state.deviceInfo) && deviceVolume == state.deviceVolume && isDeviceMuted == state.isDeviceMuted && surfaceSize.equals(state.surfaceSize) && newlyRenderedFirstFrame == state.newlyRenderedFirstFrame && timedMetadata.equals(state.timedMetadata) && playlist.equals(state.playlist) && playlistMetadata.equals(state.playlistMetadata) && currentMediaItemIndex == state.currentMediaItemIndex && currentAdGroupIndex == state.currentAdGroupIndex && currentAdIndexInAdGroup == state.currentAdIndexInAdGroup && contentPositionMsSupplier.equals(state.contentPositionMsSupplier) && adPositionMsSupplier.equals(state.adPositionMsSupplier) && contentBufferedPositionMsSupplier.equals(state.contentBufferedPositionMsSupplier) && [MASK] .equals(state. [MASK] ) && totalBufferedDurationMsSupplier.equals(state.totalBufferedDurationMsSupplier) && hasPositionDiscontinuity == state.hasPositionDiscontinuity && positionDiscontinuityReason == state.positionDiscontinuityReason && discontinuityPositionMs == state.discontinuityPositionMs; } @Override public int hashCode() { int result = 7; result = 31 * result + availableCommands.hashCode(); result = 31 * result + (playWhenReady ? 1 : 0); result = 31 * result + playWhenReadyChangeReason; result = 31 * result + playbackState; result = 31 * result + playbackSuppressionReason; result = 31 * result + (playerError == null ? 0 : playerError.hashCode()); result = 31 * result + repeatMode; result = 31 * result + (shuffleModeEnabled ? 1 : 0); result = 31 * result + (isLoading ? 1 : 0); result = 31 * result + (int) (seekBackIncrementMs ^ (seekBackIncrementMs >>> 32)); result = 31 * result + (int) (seekForwardIncrementMs ^ (seekForwardIncrementMs >>> 32)); result = 31 * result + (int) (maxSeekToPreviousPositionMs ^ (maxSeekToPreviousPositionMs >>> 32)); result = 31 * result + playbackParameters.hashCode(); result = 31 * result + trackSelectionParameters.hashCode(); result = 31 * result + audioAttributes.hashCode(); result = 31 * result + Float.floatToRawIntBits(volume); result = 31 * result + videoSize.hashCode(); result = 31 * result + currentCues.hashCode(); result = 31 * result + deviceInfo.hashCode(); result = 31 * result + deviceVolume; result = 31 * result + (isDeviceMuted ? 1 : 0); result = 31 * result + surfaceSize.hashCode(); result = 31 * result + (newlyRenderedFirstFrame ? 1 : 0); result = 31 * result + timedMetadata.hashCode(); result = 31 * result + playlist.hashCode(); result = 31 * result + playlistMetadata.hashCode(); result = 31 * result + currentMediaItemIndex; result = 31 * result + currentAdGroupIndex; result = 31 * result + currentAdIndexInAdGroup; result = 31 * result + contentPositionMsSupplier.hashCode(); result = 31 * result + adPositionMsSupplier.hashCode(); result = 31 * result + contentBufferedPositionMsSupplier.hashCode(); result = 31 * result + [MASK] .hashCode(); result = 31 * result + totalBufferedDurationMsSupplier.hashCode(); result = 31 * result + (hasPositionDiscontinuity ? 1 : 0); result = 31 * result + positionDiscontinuityReason; result = 31 * result + (int) (discontinuityPositionMs ^ (discontinuityPositionMs >>> 32)); return result; } } private static final class PlaylistTimeline extends Timeline { private final ImmutableList playlist; private final int[] firstPeriodIndexByWindowIndex; private final int[] windowIndexByPeriodIndex; private final HashMap periodIndexByUid; public PlaylistTimeline(ImmutableList playlist) { int mediaItemCount = playlist.size(); this.playlist = playlist; this.firstPeriodIndexByWindowIndex = new int[mediaItemCount]; int periodCount = 0; for (int i = 0; i < mediaItemCount; i++) { MediaItemData mediaItemData = playlist.get(i); firstPeriodIndexByWindowIndex[i] = periodCount; periodCount += getPeriodCountInMediaItem(mediaItemData); } this.windowIndexByPeriodIndex = new int[periodCount]; this.periodIndexByUid = new HashMap<>(); int periodIndex = 0; for (int i = 0; i < mediaItemCount; i++) { MediaItemData mediaItemData = playlist.get(i); for (int j = 0; j < getPeriodCountInMediaItem(mediaItemData); j++) { periodIndexByUid.put(mediaItemData.getPeriodUid(j), periodIndex); windowIndexByPeriodIndex[periodIndex] = i; periodIndex++; } } } @Override public int getWindowCount() { return playlist.size(); } @Override public int getNextWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) { // TODO: Support shuffle order. return super.getNextWindowIndex(windowIndex, repeatMode, shuffleModeEnabled); } @Override public int getPreviousWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) { // TODO: Support shuffle order. return super.getPreviousWindowIndex(windowIndex, repeatMode, shuffleModeEnabled); } @Override public int getLastWindowIndex(boolean shuffleModeEnabled) { // TODO: Support shuffle order. return super.getLastWindowIndex(shuffleModeEnabled); } @Override public int getFirstWindowIndex(boolean shuffleModeEnabled) { // TODO: Support shuffle order. return super.getFirstWindowIndex(shuffleModeEnabled); } @Override public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { return playlist .get(windowIndex) .getWindow(firstPeriodIndexByWindowIndex[windowIndex], window); } @Override public int getPeriodCount() { return windowIndexByPeriodIndex.length; } @Override public Period getPeriodByUid(Object periodUid, Period period) { int periodIndex = checkNotNull(periodIndexByUid.get(periodUid)); return getPeriod(periodIndex, period, /* setIds= */ true); } @Override public Period getPeriod(int periodIndex, Period period, boolean setIds) { int windowIndex = windowIndexByPeriodIndex[periodIndex]; int periodIndexInWindow = periodIndex - firstPeriodIndexByWindowIndex[windowIndex]; return playlist.get(windowIndex).getPeriod(windowIndex, periodIndexInWindow, period); } @Override public int getIndexOfPeriod(Object uid) { @Nullable Integer index = periodIndexByUid.get(uid); return index == null ? C.INDEX_UNSET : index; } @Override public Object getUidOfPeriod(int periodIndex) { int windowIndex = windowIndexByPeriodIndex[periodIndex]; int periodIndexInWindow = periodIndex - firstPeriodIndexByWindowIndex[windowIndex]; return playlist.get(windowIndex).getPeriodUid(periodIndexInWindow); } private static int getPeriodCountInMediaItem(MediaItemData mediaItemData) { return mediaItemData.periods.isEmpty() ? 1 : mediaItemData.periods.size(); } } /** * An immutable description of an item in the playlist, containing both static setup information * like {@link MediaItem} and dynamic data that is generally read from the media like the * duration. */ protected static final class MediaItemData { /** A builder for {@link MediaItemData} objects. */ public static final class Builder { private Object uid; private Tracks tracks; private MediaItem mediaItem; @Nullable private MediaMetadata mediaMetadata; @Nullable private Object manifest; @Nullable private MediaItem.LiveConfiguration liveConfiguration; private long presentationStartTimeMs; private long windowStartTimeMs; private long elapsedRealtimeEpochOffsetMs; private boolean isSeekable; private boolean isDynamic; private long defaultPositionUs; private long durationUs; private long positionInFirstPeriodUs; private boolean isPlaceholder; private ImmutableList periods; /** * Creates the builder. * * @param uid The unique identifier of the media item within a playlist. This value will be * set as {@link Timeline.Window#uid} for this item. */ public Builder(Object uid) { this.uid = uid; tracks = Tracks.EMPTY; mediaItem = MediaItem.EMPTY; mediaMetadata = null; manifest = null; liveConfiguration = null; presentationStartTimeMs = C.TIME_UNSET; windowStartTimeMs = C.TIME_UNSET; elapsedRealtimeEpochOffsetMs = C.TIME_UNSET; isSeekable = false; isDynamic = false; defaultPositionUs = 0; durationUs = C.TIME_UNSET; positionInFirstPeriodUs = 0; isPlaceholder = false; periods = ImmutableList.of(); } private Builder(MediaItemData mediaItemData) { this.uid = mediaItemData.uid; this.tracks = mediaItemData.tracks; this.mediaItem = mediaItemData.mediaItem; this.mediaMetadata = mediaItemData.mediaMetadata; this.manifest = mediaItemData.manifest; this.liveConfiguration = mediaItemData.liveConfiguration; this.presentationStartTimeMs = mediaItemData.presentationStartTimeMs; this.windowStartTimeMs = mediaItemData.windowStartTimeMs; this.elapsedRealtimeEpochOffsetMs = mediaItemData.elapsedRealtimeEpochOffsetMs; this.isSeekable = mediaItemData.isSeekable; this.isDynamic = mediaItemData.isDynamic; this.defaultPositionUs = mediaItemData.defaultPositionUs; this.durationUs = mediaItemData.durationUs; this.positionInFirstPeriodUs = mediaItemData.positionInFirstPeriodUs; this.isPlaceholder = mediaItemData.isPlaceholder; this.periods = mediaItemData.periods; } /** * Sets the unique identifier of this media item within a playlist. * *

This value will be set as {@link Timeline.Window#uid} for this item. * * @param uid The unique identifier of this media item within a playlist. * @return This builder. */ @CanIgnoreReturnValue public Builder setUid(Object uid) { this.uid = uid; return this; } /** * Sets the {@link Tracks} of this media item. * * @param tracks The {@link Tracks} of this media item. * @return This builder. */ @CanIgnoreReturnValue public Builder setTracks(Tracks tracks) { this.tracks = tracks; return this; } /** * Sets the {@link MediaItem}. * * @param mediaItem The {@link MediaItem}. * @return This builder. */ @CanIgnoreReturnValue public Builder setMediaItem(MediaItem mediaItem) { this.mediaItem = mediaItem; return this; } /** * Sets the {@link MediaMetadata}. * *

This data includes static data from the {@link MediaItem#mediaMetadata MediaItem} and * the media's {@link Format#metadata Format}, as well any dynamic metadata that has been * parsed from the media. If null, the metadata is assumed to be the simple combination of the * {@link MediaItem#mediaMetadata MediaItem} metadata and the metadata of the selected {@link * Format#metadata Formats}. * * @param mediaMetadata The {@link MediaMetadata}, or null to assume that the metadata is the * simple combination of the {@link MediaItem#mediaMetadata MediaItem} metadata and the * metadata of the selected {@link Format#metadata Formats}. * @return This builder. */ @CanIgnoreReturnValue public Builder setMediaMetadata(@Nullable MediaMetadata mediaMetadata) { this.mediaMetadata = mediaMetadata; return this; } /** * Sets the manifest of the media item. * * @param manifest The manifest of the media item, or null if not applicable. * @return This builder. */ @CanIgnoreReturnValue public Builder setManifest(@Nullable Object manifest) { this.manifest = manifest; return this; } /** * Sets the active {@link MediaItem.LiveConfiguration}, or null if the media item is not live. * * @param liveConfiguration The active {@link MediaItem.LiveConfiguration}, or null if the * media item is not live. * @return This builder. */ @CanIgnoreReturnValue public Builder setLiveConfiguration(@Nullable MediaItem.LiveConfiguration liveConfiguration) { this.liveConfiguration = liveConfiguration; return this; } /** * Sets the start time of the live presentation. * *

This value can only be set to anything other than {@link C#TIME_UNSET} if the stream is * {@linkplain #setLiveConfiguration live}. * * @param presentationStartTimeMs The start time of the live presentation, in milliseconds * since the Unix epoch, or {@link C#TIME_UNSET} if unknown or not applicable. * @return This builder. */ @CanIgnoreReturnValue public Builder setPresentationStartTimeMs(long presentationStartTimeMs) { this.presentationStartTimeMs = presentationStartTimeMs; return this; } /** * Sets the start time of the live window. * *

This value can only be set to anything other than {@link C#TIME_UNSET} if the stream is * {@linkplain #setLiveConfiguration live}. The value should also be greater or equal than the * {@linkplain #setPresentationStartTimeMs presentation start time}, if set. * * @param windowStartTimeMs The start time of the live window, in milliseconds since the Unix * epoch, or {@link C#TIME_UNSET} if unknown or not applicable. * @return This builder. */ @CanIgnoreReturnValue public Builder setWindowStartTimeMs(long windowStartTimeMs) { this.windowStartTimeMs = windowStartTimeMs; return this; } /** * Sets the offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix * epoch according to the clock of the media origin server. * *

This value can only be set to anything other than {@link C#TIME_UNSET} if the stream is * {@linkplain #setLiveConfiguration live}. * * @param elapsedRealtimeEpochOffsetMs The offset between {@link * SystemClock#elapsedRealtime()} and the time since the Unix epoch according to the clock * of the media origin server, or {@link C#TIME_UNSET} if unknown or not applicable. * @return This builder. */ @CanIgnoreReturnValue public Builder setElapsedRealtimeEpochOffsetMs(long elapsedRealtimeEpochOffsetMs) { this.elapsedRealtimeEpochOffsetMs = elapsedRealtimeEpochOffsetMs; return this; } /** * Sets whether it's possible to seek within this media item. * * @param isSeekable Whether it's possible to seek within this media item. * @return This builder. */ @CanIgnoreReturnValue public Builder setIsSeekable(boolean isSeekable) { this.isSeekable = isSeekable; return this; } /** * Sets whether this media item may change over time, for example a moving live window. * * @param isDynamic Whether this media item may change over time, for example a moving live * window. * @return This builder. */ @CanIgnoreReturnValue public Builder setIsDynamic(boolean isDynamic) { this.isDynamic = isDynamic; return this; } /** * Sets the default position relative to the start of the media item at which to begin * playback, in microseconds. * *

The default position must be less or equal to the {@linkplain #setDurationUs duration}, * is set. * * @param defaultPositionUs The default position relative to the start of the media item at * which to begin playback, in microseconds. * @return This builder. */ @CanIgnoreReturnValue public Builder setDefaultPositionUs(long defaultPositionUs) { checkArgument(defaultPositionUs >= 0); this.defaultPositionUs = defaultPositionUs; return this; } /** * Sets the duration of the media item, in microseconds. * *

If both this duration and all {@linkplain #setPeriods period} durations are set, the sum * of this duration and the {@linkplain #setPositionInFirstPeriodUs offset in the first * period} must match the total duration of all periods. * * @param durationUs The duration of the media item, in microseconds, or {@link C#TIME_UNSET} * if unknown. * @return This builder. */ @CanIgnoreReturnValue public Builder setDurationUs(long durationUs) { checkArgument(durationUs == C.TIME_UNSET || durationUs >= 0); this.durationUs = durationUs; return this; } /** * Sets the position of the start of this media item relative to the start of the first period * belonging to it, in microseconds. * * @param positionInFirstPeriodUs The position of the start of this media item relative to the * start of the first period belonging to it, in microseconds. * @return This builder. */ @CanIgnoreReturnValue public Builder setPositionInFirstPeriodUs(long positionInFirstPeriodUs) { checkArgument(positionInFirstPeriodUs >= 0); this.positionInFirstPeriodUs = positionInFirstPeriodUs; return this; } /** * Sets whether this media item contains placeholder information because the real information * has yet to be loaded. * * @param isPlaceholder Whether this media item contains placeholder information because the * real information has yet to be loaded. * @return This builder. */ @CanIgnoreReturnValue public Builder setIsPlaceholder(boolean isPlaceholder) { this.isPlaceholder = isPlaceholder; return this; } /** * Sets the list of {@linkplain PeriodData periods} in this media item. * *

All periods must have unique {@linkplain PeriodData.Builder#setUid UIDs} and only the * last period is allowed to have an unset {@linkplain PeriodData.Builder#setDurationUs * duration}. * * @param periods The list of {@linkplain PeriodData periods} in this media item, or an empty * list to assume a single period without ads and the same duration as the media item. * @return This builder. */ @CanIgnoreReturnValue public Builder setPeriods(List periods) { int periodCount = periods.size(); for (int i = 0; i < periodCount - 1; i++) { checkArgument( periods.get(i).durationUs != C.TIME_UNSET, ""Periods other than last need a duration""); for (int j = i + 1; j < periodCount; j++) { checkArgument( !periods.get(i).uid.equals(periods.get(j).uid), ""Duplicate PeriodData UIDs in period list""); } } this.periods = ImmutableList.copyOf(periods); return this; } /** Builds the {@link MediaItemData}. */ public MediaItemData build() { return new MediaItemData(this); } } /** The unique identifier of this media item. */ public final Object uid; /** The {@link Tracks} of this media item. */ public final Tracks tracks; /** The {@link MediaItem}. */ public final MediaItem mediaItem; /** * The {@link MediaMetadata}, including static data from the {@link MediaItem#mediaMetadata * MediaItem} and the media's {@link Format#metadata Format}, as well any dynamic metadata that * has been parsed from the media. If null, the metadata is assumed to be the simple combination * of the {@link MediaItem#mediaMetadata MediaItem} metadata and the metadata of the selected * {@link Format#metadata Formats}. */ @Nullable public final MediaMetadata mediaMetadata; /** The manifest of the media item, or null if not applicable. */ @Nullable public final Object manifest; /** The active {@link MediaItem.LiveConfiguration}, or null if the media item is not live. */ @Nullable public final MediaItem.LiveConfiguration liveConfiguration; /** * The start time of the live presentation, in milliseconds since the Unix epoch, or {@link * C#TIME_UNSET} if unknown or not applicable. */ public final long presentationStartTimeMs; /** * The start time of the live window, in milliseconds since the Unix epoch, or {@link * C#TIME_UNSET} if unknown or not applicable. */ public final long windowStartTimeMs; /** * The offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix epoch * according to the clock of the media origin server, or {@link C#TIME_UNSET} if unknown or not * applicable. */ public final long elapsedRealtimeEpochOffsetMs; /** Whether it's possible to seek within this media item. */ public final boolean isSeekable; /** Whether this media item may change over time, for example a moving live window. */ public final boolean isDynamic; /** * The default position relative to the start of the media item at which to begin playback, in * microseconds. */ public final long defaultPositionUs; /** The duration of the media item, in microseconds, or {@link C#TIME_UNSET} if unknown. */ public final long durationUs; /** * The position of the start of this media item relative to the start of the first period * belonging to it, in microseconds. */ public final long positionInFirstPeriodUs; /** * Whether this media item contains placeholder information because the real information has yet * to be loaded. */ public final boolean isPlaceholder; /** * The list of {@linkplain PeriodData periods} in this media item, or an empty list to assume a * single period without ads and the same duration as the media item. */ public final ImmutableList periods; private final long[] periodPositionInWindowUs; private final MediaMetadata combinedMediaMetadata; private MediaItemData(Builder builder) { if (builder.liveConfiguration == null) { checkArgument( builder.presentationStartTimeMs == C.TIME_UNSET, ""presentationStartTimeMs can only be set if liveConfiguration != null""); checkArgument( builder.windowStartTimeMs == C.TIME_UNSET, ""windowStartTimeMs can only be set if liveConfiguration != null""); checkArgument( builder.elapsedRealtimeEpochOffsetMs == C.TIME_UNSET, ""elapsedRealtimeEpochOffsetMs can only be set if liveConfiguration != null""); } else if (builder.presentationStartTimeMs != C.TIME_UNSET && builder.windowStartTimeMs != C.TIME_UNSET) { checkArgument( builder.windowStartTimeMs >= builder.presentationStartTimeMs, ""windowStartTimeMs can't be less than presentationStartTimeMs""); } int periodCount = builder.periods.size(); if (builder.durationUs != C.TIME_UNSET) { checkArgument( builder.defaultPositionUs <= builder.durationUs, ""defaultPositionUs can't be greater than durationUs""); } this.uid = builder.uid; this.tracks = builder.tracks; this.mediaItem = builder.mediaItem; this.mediaMetadata = builder.mediaMetadata; this.manifest = builder.manifest; this.liveConfiguration = builder.liveConfiguration; this.presentationStartTimeMs = builder.presentationStartTimeMs; this.windowStartTimeMs = builder.windowStartTimeMs; this.elapsedRealtimeEpochOffsetMs = builder.elapsedRealtimeEpochOffsetMs; this.isSeekable = builder.isSeekable; this.isDynamic = builder.isDynamic; this.defaultPositionUs = builder.defaultPositionUs; this.durationUs = builder.durationUs; this.positionInFirstPeriodUs = builder.positionInFirstPeriodUs; this.isPlaceholder = builder.isPlaceholder; this.periods = builder.periods; periodPositionInWindowUs = new long[periods.size()]; if (!periods.isEmpty()) { periodPositionInWindowUs[0] = -positionInFirstPeriodUs; for (int i = 0; i < periodCount - 1; i++) { periodPositionInWindowUs[i + 1] = periodPositionInWindowUs[i] + periods.get(i).durationUs; } } combinedMediaMetadata = mediaMetadata != null ? mediaMetadata : getCombinedMediaMetadata(mediaItem, tracks); } /** Returns a {@link Builder} pre-populated with the current values. */ public Builder buildUpon() { return new Builder(this); } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (!(o instanceof MediaItemData)) { return false; } MediaItemData mediaItemData = (MediaItemData) o; return this.uid.equals(mediaItemData.uid) && this.tracks.equals(mediaItemData.tracks) && this.mediaItem.equals(mediaItemData.mediaItem) && Util.areEqual(this.mediaMetadata, mediaItemData.mediaMetadata) && Util.areEqual(this.manifest, mediaItemData.manifest) && Util.areEqual(this.liveConfiguration, mediaItemData.liveConfiguration) && this.presentationStartTimeMs == mediaItemData.presentationStartTimeMs && this.windowStartTimeMs == mediaItemData.windowStartTimeMs && this.elapsedRealtimeEpochOffsetMs == mediaItemData.elapsedRealtimeEpochOffsetMs && this.isSeekable == mediaItemData.isSeekable && this.isDynamic == mediaItemData.isDynamic && this.defaultPositionUs == mediaItemData.defaultPositionUs && this.durationUs == mediaItemData.durationUs && this.positionInFirstPeriodUs == mediaItemData.positionInFirstPeriodUs && this.isPlaceholder == mediaItemData.isPlaceholder && this.periods.equals(mediaItemData.periods); } @Override public int hashCode() { int result = 7; result = 31 * result + uid.hashCode(); result = 31 * result + tracks.hashCode(); result = 31 * result + mediaItem.hashCode(); result = 31 * result + (mediaMetadata == null ? 0 : mediaMetadata.hashCode()); result = 31 * result + (manifest == null ? 0 : manifest.hashCode()); result = 31 * result + (liveConfiguration == null ? 0 : liveConfiguration.hashCode()); result = 31 * result + (int) (presentationStartTimeMs ^ (presentationStartTimeMs >>> 32)); result = 31 * result + (int) (windowStartTimeMs ^ (windowStartTimeMs >>> 32)); result = 31 * result + (int) (elapsedRealtimeEpochOffsetMs ^ (elapsedRealtimeEpochOffsetMs >>> 32)); result = 31 * result + (isSeekable ? 1 : 0); result = 31 * result + (isDynamic ? 1 : 0); result = 31 * result + (int) (defaultPositionUs ^ (defaultPositionUs >>> 32)); result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32)); result = 31 * result + (isPlaceholder ? 1 : 0); result = 31 * result + periods.hashCode(); return result; } private Timeline.Window getWindow(int firstPeriodIndex, Timeline.Window window) { int periodCount = periods.isEmpty() ? 1 : periods.size(); window.set( uid, mediaItem, manifest, presentationStartTimeMs, windowStartTimeMs, elapsedRealtimeEpochOffsetMs, isSeekable, isDynamic, liveConfiguration, defaultPositionUs, durationUs, firstPeriodIndex, /* lastPeriodIndex= */ firstPeriodIndex + periodCount - 1, positionInFirstPeriodUs); window.isPlaceholder = isPlaceholder; return window; } private Timeline.Period getPeriod( int windowIndex, int periodIndexInMediaItem, Timeline.Period period) { if (periods.isEmpty()) { period.set( /* id= */ uid, uid, windowIndex, /* durationUs= */ positionInFirstPeriodUs + durationUs, /* positionInWindowUs= */ 0, AdPlaybackState.NONE, isPlaceholder); } else { PeriodData periodData = periods.get(periodIndexInMediaItem); Object periodId = periodData.uid; Object periodUid = Pair.create(uid, periodId); period.set( periodId, periodUid, windowIndex, periodData.durationUs, periodPositionInWindowUs[periodIndexInMediaItem], periodData.adPlaybackState, periodData.isPlaceholder); } return period; } private Object getPeriodUid(int periodIndexInMediaItem) { if (periods.isEmpty()) { return uid; } Object periodId = periods.get(periodIndexInMediaItem).uid; return Pair.create(uid, periodId); } private static MediaMetadata getCombinedMediaMetadata(MediaItem mediaItem, Tracks tracks) { MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); int trackGroupCount = tracks.getGroups().size(); for (int i = 0; i < trackGroupCount; i++) { Tracks.Group group = tracks.getGroups().get(i); for (int j = 0; j < group.length; j++) { if (group.isTrackSelected(j)) { Format format = group.getTrackFormat(j); if (format.metadata != null) { for (int k = 0; k < format.metadata.length(); k++) { format.metadata.get(k).populateMediaMetadata(metadataBuilder); } } } } } return metadataBuilder.populate(mediaItem.mediaMetadata).build(); } } /** Data describing the properties of a period inside a {@link MediaItemData}. */ protected static final class PeriodData { /** A builder for {@link PeriodData} objects. */ public static final class Builder { private Object uid; private long durationUs; private AdPlaybackState adPlaybackState; private boolean isPlaceholder; /** * Creates the builder. * * @param uid The unique identifier of the period within its media item. */ public Builder(Object uid) { this.uid = uid; this.durationUs = 0; this.adPlaybackState = AdPlaybackState.NONE; this.isPlaceholder = false; } private Builder(PeriodData periodData) { this.uid = periodData.uid; this.durationUs = periodData.durationUs; this.adPlaybackState = periodData.adPlaybackState; this.isPlaceholder = periodData.isPlaceholder; } /** * Sets the unique identifier of the period within its media item. * * @param uid The unique identifier of the period within its media item. * @return This builder. */ @CanIgnoreReturnValue public Builder setUid(Object uid) { this.uid = uid; return this; } /** * Sets the total duration of the period, in microseconds, or {@link C#TIME_UNSET} if unknown. * *

Only the last period in a media item can have an unknown duration. * * @param durationUs The total duration of the period, in microseconds, or {@link * C#TIME_UNSET} if unknown. * @return This builder. */ @CanIgnoreReturnValue public Builder setDurationUs(long durationUs) { checkArgument(durationUs == C.TIME_UNSET || durationUs >= 0); this.durationUs = durationUs; return this; } /** * Sets the {@link AdPlaybackState}. * * @param adPlaybackState The {@link AdPlaybackState}, or {@link AdPlaybackState#NONE} if * there are no ads. * @return This builder. */ @CanIgnoreReturnValue public Builder setAdPlaybackState(AdPlaybackState adPlaybackState) { this.adPlaybackState = adPlaybackState; return this; } /** * Sets whether this period contains placeholder information because the real information has * yet to be loaded * * @param isPlaceholder Whether this period contains placeholder information because the real * information has yet to be loaded. * @return This builder. */ @CanIgnoreReturnValue public Builder setIsPlaceholder(boolean isPlaceholder) { this.isPlaceholder = isPlaceholder; return this; } /** Builds the {@link PeriodData}. */ public PeriodData build() { return new PeriodData(this); } } /** The unique identifier of the period within its media item. */ public final Object uid; /** * The total duration of the period, in microseconds, or {@link C#TIME_UNSET} if unknown. Only * the last period in a media item can have an unknown duration. */ public final long durationUs; /** * The {@link AdPlaybackState} of the period, or {@link AdPlaybackState#NONE} if there are no * ads. */ public final AdPlaybackState adPlaybackState; /** * Whether this period contains placeholder information because the real information has yet to * be loaded. */ public final boolean isPlaceholder; private PeriodData(Builder builder) { this.uid = builder.uid; this.durationUs = builder.durationUs; this.adPlaybackState = builder.adPlaybackState; this.isPlaceholder = builder.isPlaceholder; } /** Returns a {@link Builder} pre-populated with the current values. */ public Builder buildUpon() { return new Builder(this); } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (!(o instanceof PeriodData)) { return false; } PeriodData periodData = (PeriodData) o; return this.uid.equals(periodData.uid) && this.durationUs == periodData.durationUs && this.adPlaybackState.equals(periodData.adPlaybackState) && this.isPlaceholder == periodData.isPlaceholder; } @Override public int hashCode() { int result = 7; result = 31 * result + uid.hashCode(); result = 31 * result + (int) (durationUs ^ (durationUs >>> 32)); result = 31 * result + adPlaybackState.hashCode(); result = 31 * result + (isPlaceholder ? 1 : 0); return result; } } /** A supplier for a position. */ protected interface PositionSupplier { /** An instance returning a constant position of zero. */ PositionSupplier ZERO = getConstant(/* positionMs= */ 0); /** * Returns an instance that returns a constant value. * * @param positionMs The constant position to return, in milliseconds. */ static PositionSupplier getConstant(long positionMs) { return () -> positionMs; } /** * Returns an instance that extrapolates the provided position into the future. * * @param currentPositionMs The current position in milliseconds. * @param playbackSpeed The playback speed with which the position is assumed to increase. */ static PositionSupplier getExtrapolating(long currentPositionMs, float playbackSpeed) { long startTimeMs = SystemClock.elapsedRealtime(); return () -> { long currentTimeMs = SystemClock.elapsedRealtime(); return currentPositionMs + (long) ((currentTimeMs - startTimeMs) * playbackSpeed); }; } /** Returns the position. */ long get(); } /** * Position difference threshold below which we do not automatically report a position * discontinuity, in milliseconds. */ private static final long POSITION_DISCONTINUITY_THRESHOLD_MS = 1000; private final ListenerSet listeners; private final Looper applicationLooper; private final HandlerWrapper applicationHandler; private final HashSet> pendingOperations; private final Timeline.Period period; private @MonotonicNonNull State state; private boolean released; /** * Creates the base class. * * @param applicationLooper The {@link Looper} that must be used for all calls to the player and * that is used to call listeners on. */ protected SimpleBasePlayer(Looper applicationLooper) { this(applicationLooper, Clock.DEFAULT); } /** * Creates the base class. * * @param applicationLooper The {@link Looper} that must be used for all calls to the player and * that is used to call listeners on. * @param clock The {@link Clock} that will be used by the player. */ protected SimpleBasePlayer(Looper applicationLooper, Clock clock) { this.applicationLooper = applicationLooper; applicationHandler = clock.createHandler(applicationLooper, /* callback= */ null); pendingOperations = new HashSet<>(); period = new Timeline.Period(); @SuppressWarnings(""nullness:argument.type.incompatible"") // Using this in constructor. ListenerSet listenerSet = new ListenerSet<>( applicationLooper, clock, (listener, flags) -> listener.onEvents(/* player= */ this, new Events(flags))); listeners = listenerSet; } @Override public final void addListener(Listener listener) { // Don't verify application thread. We allow calls to this method from any thread. listeners.add(checkNotNull(listener)); } @Override public final void removeListener(Listener listener) { verifyApplicationThreadAndInitState(); listeners.remove(listener); } @Override public final Looper getApplicationLooper() { // Don't verify application thread. We allow calls to this method from any thread. return applicationLooper; } @Override public final Commands getAvailableCommands() { verifyApplicationThreadAndInitState(); return state.availableCommands; } @Override public final void setPlayWhenReady(boolean playWhenReady) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_PLAY_PAUSE)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetPlayWhenReady(playWhenReady), /* placeholderStateSupplier= */ () -> state .buildUpon() .setPlayWhenReady(playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) .build()); } @Override public final boolean getPlayWhenReady() { verifyApplicationThreadAndInitState(); return state.playWhenReady; } @Override public final void setMediaItems(List mediaItems, boolean resetPosition) { verifyApplicationThreadAndInitState(); int startIndex = resetPosition ? C.INDEX_UNSET : state.currentMediaItemIndex; long startPositionMs = resetPosition ? C.TIME_UNSET : state.contentPositionMsSupplier.get(); setMediaItemsInternal(mediaItems, startIndex, startPositionMs); } @Override public final void setMediaItems( List mediaItems, int startIndex, long startPositionMs) { verifyApplicationThreadAndInitState(); if (startIndex == C.INDEX_UNSET) { startIndex = state.currentMediaItemIndex; startPositionMs = state.contentPositionMsSupplier.get(); } setMediaItemsInternal(mediaItems, startIndex, startPositionMs); } @RequiresNonNull(""state"") private void setMediaItemsInternal( List mediaItems, int startIndex, long startPositionMs) { checkArgument(startIndex == C.INDEX_UNSET || startIndex >= 0); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) && (mediaItems.size() != 1 || !shouldHandleCommand(Player.COMMAND_SET_MEDIA_ITEM))) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetMediaItems(mediaItems, startIndex, startPositionMs), /* placeholderStateSupplier= */ () -> { ArrayList placeholderPlaylist = new ArrayList<>(); for (int i = 0; i < mediaItems.size(); i++) { placeholderPlaylist.add(getPlaceholderMediaItemData(mediaItems.get(i))); } return getStateWithNewPlaylistAndPosition( state, placeholderPlaylist, startIndex, startPositionMs); }); } @Override public final void addMediaItems(int index, List mediaItems) { verifyApplicationThreadAndInitState(); checkArgument(index >= 0); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; int playlistSize = state.playlist.size(); if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || mediaItems.isEmpty()) { return; } int correctedIndex = min(index, playlistSize); updateStateForPendingOperation( /* pendingOperation= */ handleAddMediaItems(correctedIndex, mediaItems), /* placeholderStateSupplier= */ () -> { ArrayList placeholderPlaylist = new ArrayList<>(state.playlist); for (int i = 0; i < mediaItems.size(); i++) { placeholderPlaylist.add( i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i))); } if (!state.playlist.isEmpty()) { return getStateWithNewPlaylist(state, placeholderPlaylist, period); } else { // Handle initial position update when these are the first items added to the playlist. return getStateWithNewPlaylistAndPosition( state, placeholderPlaylist, state.currentMediaItemIndex, state.contentPositionMsSupplier.get()); } }); } @Override public final void moveMediaItems(int fromIndex, int toIndex, int newIndex) { verifyApplicationThreadAndInitState(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex && newIndex >= 0); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; int playlistSize = state.playlist.size(); if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || playlistSize == 0 || fromIndex >= playlistSize) { return; } int correctedToIndex = min(toIndex, playlistSize); int correctedNewIndex = min(newIndex, state.playlist.size() - (correctedToIndex - fromIndex)); if (fromIndex == correctedToIndex || correctedNewIndex == fromIndex) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleMoveMediaItems( fromIndex, correctedToIndex, correctedNewIndex), /* placeholderStateSupplier= */ () -> { ArrayList placeholderPlaylist = new ArrayList<>(state.playlist); Util.moveItems(placeholderPlaylist, fromIndex, correctedToIndex, correctedNewIndex); return getStateWithNewPlaylist(state, placeholderPlaylist, period); }); } @Override public final void replaceMediaItems(int fromIndex, int toIndex, List mediaItems) { verifyApplicationThreadAndInitState(); checkArgument(fromIndex >= 0 && fromIndex <= toIndex); State state = this.state; int playlistSize = state.playlist.size(); if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || fromIndex > playlistSize) { return; } int correctedToIndex = min(toIndex, playlistSize); updateStateForPendingOperation( /* pendingOperation= */ handleReplaceMediaItems(fromIndex, correctedToIndex, mediaItems), /* placeholderStateSupplier= */ () -> { ArrayList placeholderPlaylist = new ArrayList<>(state.playlist); for (int i = 0; i < mediaItems.size(); i++) { placeholderPlaylist.add( i + correctedToIndex, getPlaceholderMediaItemData(mediaItems.get(i))); } State updatedState; if (!state.playlist.isEmpty()) { updatedState = getStateWithNewPlaylist(state, placeholderPlaylist, period); } else { // Handle initial position update when these are the first items added to the playlist. updatedState = getStateWithNewPlaylistAndPosition( state, placeholderPlaylist, state.currentMediaItemIndex, state.contentPositionMsSupplier.get()); } if (fromIndex < correctedToIndex) { Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex); return getStateWithNewPlaylist(updatedState, placeholderPlaylist, period); } else { return updatedState; } }); } @Override public final void removeMediaItems(int fromIndex, int toIndex) { verifyApplicationThreadAndInitState(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; int playlistSize = state.playlist.size(); if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || playlistSize == 0 || fromIndex >= playlistSize) { return; } int correctedToIndex = min(toIndex, playlistSize); if (fromIndex == correctedToIndex) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleRemoveMediaItems(fromIndex, correctedToIndex), /* placeholderStateSupplier= */ () -> { ArrayList placeholderPlaylist = new ArrayList<>(state.playlist); Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex); return getStateWithNewPlaylist(state, placeholderPlaylist, period); }); } @Override public final void prepare() { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_PREPARE)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handlePrepare(), /* placeholderStateSupplier= */ () -> state .buildUpon() .setPlayerError(null) .setPlaybackState(state.timeline.isEmpty() ? STATE_ENDED : STATE_BUFFERING) .build()); } @Override @Player.State public final int getPlaybackState() { verifyApplicationThreadAndInitState(); return state.playbackState; } @Override public final int getPlaybackSuppressionReason() { verifyApplicationThreadAndInitState(); return state.playbackSuppressionReason; } @Nullable @Override public final PlaybackException getPlayerError() { verifyApplicationThreadAndInitState(); return state.playerError; } @Override public final void setRepeatMode(@Player.RepeatMode int repeatMode) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_REPEAT_MODE)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetRepeatMode(repeatMode), /* placeholderStateSupplier= */ () -> state.buildUpon().setRepeatMode(repeatMode).build()); } @Override @Player.RepeatMode public final int getRepeatMode() { verifyApplicationThreadAndInitState(); return state.repeatMode; } @Override public final void setShuffleModeEnabled(boolean shuffleModeEnabled) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_SHUFFLE_MODE)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetShuffleModeEnabled(shuffleModeEnabled), /* placeholderStateSupplier= */ () -> state.buildUpon().setShuffleModeEnabled(shuffleModeEnabled).build()); } @Override public final boolean getShuffleModeEnabled() { verifyApplicationThreadAndInitState(); return state.shuffleModeEnabled; } @Override public final boolean isLoading() { verifyApplicationThreadAndInitState(); return state.isLoading; } @Override @VisibleForTesting(otherwise = PROTECTED) public final void seekTo( int mediaItemIndex, long positionMs, @Player.Command int seekCommand, boolean isRepeatingCurrentItem) { verifyApplicationThreadAndInitState(); checkArgument(mediaItemIndex >= 0); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(seekCommand) || isPlayingAd() || (!state.playlist.isEmpty() && mediaItemIndex >= state.playlist.size())) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSeek(mediaItemIndex, positionMs, seekCommand), /* placeholderStateSupplier= */ () -> getStateWithNewPlaylistAndPosition(state, state.playlist, mediaItemIndex, positionMs), /* seeked= */ true, isRepeatingCurrentItem); } @Override public final long getSeekBackIncrement() { verifyApplicationThreadAndInitState(); return state.seekBackIncrementMs; } @Override public final long getSeekForwardIncrement() { verifyApplicationThreadAndInitState(); return state.seekForwardIncrementMs; } @Override public final long getMaxSeekToPreviousPosition() { verifyApplicationThreadAndInitState(); return state.maxSeekToPreviousPositionMs; } @Override public final void setPlaybackParameters(PlaybackParameters playbackParameters) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_SPEED_AND_PITCH)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetPlaybackParameters(playbackParameters), /* placeholderStateSupplier= */ () -> state.buildUpon().setPlaybackParameters(playbackParameters).build()); } @Override public final PlaybackParameters getPlaybackParameters() { verifyApplicationThreadAndInitState(); return state.playbackParameters; } @Override public final void stop() { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_STOP)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleStop(), /* placeholderStateSupplier= */ () -> state .buildUpon() .setPlaybackState(Player.STATE_IDLE) .setTotalBufferedDurationMs(PositionSupplier.ZERO) .setContentBufferedPositionMs( PositionSupplier.getConstant(getContentPositionMsInternal(state))) .setAdBufferedPositionMs(state.adPositionMsSupplier) .setIsLoading(false) .build()); } @Override public final void release() { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_RELEASE)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleRelease(), /* placeholderStateSupplier= */ () -> state); released = true; listeners.release(); // Enforce some final state values in case getters are called after release. this.state = this.state .buildUpon() .setPlaybackState(Player.STATE_IDLE) .setTotalBufferedDurationMs(PositionSupplier.ZERO) .setContentBufferedPositionMs( PositionSupplier.getConstant(getContentPositionMsInternal(state))) .setAdBufferedPositionMs(state.adPositionMsSupplier) .setIsLoading(false) .build(); } @Override public final Tracks getCurrentTracks() { verifyApplicationThreadAndInitState(); return getCurrentTracksInternal(state); } @Override public final TrackSelectionParameters getTrackSelectionParameters() { verifyApplicationThreadAndInitState(); return state.trackSelectionParameters; } @Override public final void setTrackSelectionParameters(TrackSelectionParameters parameters) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetTrackSelectionParameters(parameters), /* placeholderStateSupplier= */ () -> state.buildUpon().setTrackSelectionParameters(parameters).build()); } @Override public final MediaMetadata getMediaMetadata() { verifyApplicationThreadAndInitState(); return getMediaMetadataInternal(state); } @Override public final MediaMetadata getPlaylistMetadata() { verifyApplicationThreadAndInitState(); return state.playlistMetadata; } @Override public final void setPlaylistMetadata(MediaMetadata mediaMetadata) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_PLAYLIST_METADATA)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetPlaylistMetadata(mediaMetadata), /* placeholderStateSupplier= */ () -> state.buildUpon().setPlaylistMetadata(mediaMetadata).build()); } @Override public final Timeline getCurrentTimeline() { verifyApplicationThreadAndInitState(); return state.timeline; } @Override public final int getCurrentPeriodIndex() { verifyApplicationThreadAndInitState(); return getCurrentPeriodIndexInternal(state, window, period); } @Override public final int getCurrentMediaItemIndex() { verifyApplicationThreadAndInitState(); return getCurrentMediaItemIndexInternal(state); } @Override public final long getDuration() { verifyApplicationThreadAndInitState(); if (isPlayingAd()) { state.timeline.getPeriod(getCurrentPeriodIndex(), period); long adDurationUs = period.getAdDurationUs(state.currentAdGroupIndex, state.currentAdIndexInAdGroup); return Util.usToMs(adDurationUs); } return getContentDuration(); } @Override public final long getCurrentPosition() { verifyApplicationThreadAndInitState(); return isPlayingAd() ? state.adPositionMsSupplier.get() : getContentPosition(); } @Override public final long getBufferedPosition() { verifyApplicationThreadAndInitState(); return isPlayingAd() ? max(state. [MASK] .get(), state.adPositionMsSupplier.get()) : getContentBufferedPosition(); } @Override public final long getTotalBufferedDuration() { verifyApplicationThreadAndInitState(); return state.totalBufferedDurationMsSupplier.get(); } @Override public final boolean isPlayingAd() { verifyApplicationThreadAndInitState(); return state.currentAdGroupIndex != C.INDEX_UNSET; } @Override public final int getCurrentAdGroupIndex() { verifyApplicationThreadAndInitState(); return state.currentAdGroupIndex; } @Override public final int getCurrentAdIndexInAdGroup() { verifyApplicationThreadAndInitState(); return state.currentAdIndexInAdGroup; } @Override public final long getContentPosition() { verifyApplicationThreadAndInitState(); return getContentPositionMsInternal(state); } @Override public final long getContentBufferedPosition() { verifyApplicationThreadAndInitState(); return max(getContentBufferedPositionMsInternal(state), getContentPositionMsInternal(state)); } @Override public final AudioAttributes getAudioAttributes() { verifyApplicationThreadAndInitState(); return state.audioAttributes; } @Override public final void setVolume(float volume) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_VOLUME)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetVolume(volume), /* placeholderStateSupplier= */ () -> state.buildUpon().setVolume(volume).build()); } @Override public final float getVolume() { verifyApplicationThreadAndInitState(); return state.volume; } @Override public final void setVideoSurface(@Nullable Surface surface) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (surface == null) { clearVideoSurface(); return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetVideoOutput(surface), /* placeholderStateSupplier= */ () -> state.buildUpon().setSurfaceSize(Size.UNKNOWN).build()); } @Override public final void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (surfaceHolder == null) { clearVideoSurface(); return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetVideoOutput(surfaceHolder), /* placeholderStateSupplier= */ () -> state.buildUpon().setSurfaceSize(getSurfaceHolderSize(surfaceHolder)).build()); } @Override public final void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (surfaceView == null) { clearVideoSurface(); return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetVideoOutput(surfaceView), /* placeholderStateSupplier= */ () -> state .buildUpon() .setSurfaceSize(getSurfaceHolderSize(surfaceView.getHolder())) .build()); } @Override public final void setVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } if (textureView == null) { clearVideoSurface(); return; } Size surfaceSize; if (textureView.isAvailable()) { surfaceSize = new Size(textureView.getWidth(), textureView.getHeight()); } else { surfaceSize = Size.ZERO; } updateStateForPendingOperation( /* pendingOperation= */ handleSetVideoOutput(textureView), /* placeholderStateSupplier= */ () -> state.buildUpon().setSurfaceSize(surfaceSize).build()); } @Override public final void clearVideoSurface() { clearVideoOutput(/* videoOutput= */ null); } @Override public final void clearVideoSurface(@Nullable Surface surface) { clearVideoOutput(surface); } @Override public final void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { clearVideoOutput(surfaceHolder); } @Override public final void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { clearVideoOutput(surfaceView); } @Override public final void clearVideoTextureView(@Nullable TextureView textureView) { clearVideoOutput(textureView); } private void clearVideoOutput(@Nullable Object videoOutput) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_VIDEO_SURFACE)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleClearVideoOutput(videoOutput), /* placeholderStateSupplier= */ () -> state.buildUpon().setSurfaceSize(Size.ZERO).build()); } @Override public final VideoSize getVideoSize() { verifyApplicationThreadAndInitState(); return state.videoSize; } @Override public final Size getSurfaceSize() { verifyApplicationThreadAndInitState(); return state.surfaceSize; } @Override public final CueGroup getCurrentCues() { verifyApplicationThreadAndInitState(); return state.currentCues; } @Override public final DeviceInfo getDeviceInfo() { verifyApplicationThreadAndInitState(); return state.deviceInfo; } @Override public final int getDeviceVolume() { verifyApplicationThreadAndInitState(); return state.deviceVolume; } @Override public final boolean isDeviceMuted() { verifyApplicationThreadAndInitState(); return state.isDeviceMuted; } /** * @deprecated Use {@link #setDeviceVolume(int, int)} instead. */ @Deprecated @Override public final void setDeviceVolume(int volume) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetDeviceVolume(volume, C.VOLUME_FLAG_SHOW_UI), /* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(volume).build()); } @Override public final void setDeviceVolume(int volume, @C.VolumeFlags int flags) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetDeviceVolume(volume, flags), /* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(volume).build()); } /** * @deprecated Use {@link #increaseDeviceVolume(int)} instead. */ @Deprecated @Override public final void increaseDeviceVolume() { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleIncreaseDeviceVolume(C.VOLUME_FLAG_SHOW_UI), /* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(state.deviceVolume + 1).build()); } @Override public final void increaseDeviceVolume(@C.VolumeFlags int flags) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleIncreaseDeviceVolume(flags), /* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(state.deviceVolume + 1).build()); } /** * @deprecated Use {@link #decreaseDeviceVolume(int)} instead. */ @Deprecated @Override public final void decreaseDeviceVolume() { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleDecreaseDeviceVolume(C.VOLUME_FLAG_SHOW_UI), /* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(max(0, state.deviceVolume - 1)).build()); } @Override public final void decreaseDeviceVolume(@C.VolumeFlags int flags) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleDecreaseDeviceVolume(flags), /* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(max(0, state.deviceVolume - 1)).build()); } /** * @deprecated Use {@link #setDeviceMuted(boolean, int)} instead. */ @Deprecated @Override public final void setDeviceMuted(boolean muted) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetDeviceMuted(muted, C.VOLUME_FLAG_SHOW_UI), /* placeholderStateSupplier= */ () -> state.buildUpon().setIsDeviceMuted(muted).build()); } @Override public final void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) { verifyApplicationThreadAndInitState(); // Use a local copy to ensure the lambda below uses the current state value. State state = this.state; if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) { return; } updateStateForPendingOperation( /* pendingOperation= */ handleSetDeviceMuted(muted, flags), /* placeholderStateSupplier= */ () -> state.buildUpon().setIsDeviceMuted(muted).build()); } /** * Invalidates the current state. * *

Triggers a call to {@link #getState()} and informs listeners if the state changed. * *

Note that this may not have an immediate effect while there are still player methods being * handled asynchronously. The state will be invalidated automatically once these pending * synchronous operations are finished and there is no need to call this method again. */ protected final void invalidateState() { verifyApplicationThreadAndInitState(); if (!pendingOperations.isEmpty() || released) { return; } updateStateAndInformListeners( getState(), /* seeked= */ false, /* isRepeatingCurrentItem= */ false); } /** * Returns the current {@link State} of the player. * *

The {@link State} should include all {@linkplain * State.Builder#setAvailableCommands(Commands) available commands} indicating which player * methods are allowed to be called. * *

Note that this method won't be called while asynchronous handling of player methods is in * progress. This means that the implementation doesn't need to handle state changes caused by * these asynchronous operations until they are done and can return the currently known state * directly. The placeholder state used while these asynchronous operations are in progress can be * customized by overriding {@link #getPlaceholderState(State)} if required. */ @ForOverride protected abstract State getState(); /** * Returns the placeholder state used while a player method is handled asynchronously. * *

The {@code suggestedPlaceholderState} already contains the most likely state update, for * example setting {@link State#playWhenReady} to true if {@code player.setPlayWhenReady(true)} is * called, and an implementations only needs to override this method if it can determine a more * accurate placeholder state. * * @param suggestedPlaceholderState The suggested placeholder {@link State}, including the most * likely outcome of handling all pending asynchronous operations. * @return The placeholder {@link State} to use while asynchronous operations are pending. */ @ForOverride protected State getPlaceholderState(State suggestedPlaceholderState) { return suggestedPlaceholderState; } /** * Returns the placeholder {@link MediaItemData} used for a new {@link MediaItem} added to the * playlist. * *

An implementation only needs to override this method if it can determine a more accurate * placeholder state than the default. * * @param mediaItem The {@link MediaItem} added to the playlist. * @return The {@link MediaItemData} used as placeholder while adding the item to the playlist is * in progress. */ @ForOverride protected MediaItemData getPlaceholderMediaItemData(MediaItem mediaItem) { return new MediaItemData.Builder(new PlaceholderUid()) .setMediaItem(mediaItem) .setIsDynamic(true) .setIsPlaceholder(true) .build(); } /** * Handles calls to {@link Player#setPlayWhenReady}, {@link Player#play} and {@link Player#pause}. * *

Will only be called if {@link Player#COMMAND_PLAY_PAUSE} is available. * * @param playWhenReady The requested {@link State#playWhenReady} * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetPlayWhenReady(boolean playWhenReady) { throw new IllegalStateException(""Missing implementation to handle COMMAND_PLAY_PAUSE""); } /** * Handles calls to {@link Player#prepare}. * *

Will only be called if {@link Player#COMMAND_PREPARE} is available. * * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handlePrepare() { throw new IllegalStateException(""Missing implementation to handle COMMAND_PREPARE""); } /** * Handles calls to {@link Player#stop}. * *

Will only be called if {@link Player#COMMAND_STOP} is available. * * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleStop() { throw new IllegalStateException(""Missing implementation to handle COMMAND_STOP""); } /** * Handles calls to {@link Player#release}. * *

Will only be called if {@link Player#COMMAND_RELEASE} is available. * * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleRelease() { throw new IllegalStateException(""Missing implementation to handle COMMAND_RELEASE""); } /** * Handles calls to {@link Player#setRepeatMode}. * *

Will only be called if {@link Player#COMMAND_SET_REPEAT_MODE} is available. * * @param repeatMode The requested {@link RepeatMode}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetRepeatMode(@RepeatMode int repeatMode) { throw new IllegalStateException(""Missing implementation to handle COMMAND_SET_REPEAT_MODE""); } /** * Handles calls to {@link Player#setShuffleModeEnabled}. * *

Will only be called if {@link Player#COMMAND_SET_SHUFFLE_MODE} is available. * * @param shuffleModeEnabled Whether shuffle mode was requested to be enabled. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetShuffleModeEnabled(boolean shuffleModeEnabled) { throw new IllegalStateException(""Missing implementation to handle COMMAND_SET_SHUFFLE_MODE""); } /** * Handles calls to {@link Player#setPlaybackParameters} or {@link Player#setPlaybackSpeed}. * *

Will only be called if {@link Player#COMMAND_SET_SPEED_AND_PITCH} is available. * * @param playbackParameters The requested {@link PlaybackParameters}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetPlaybackParameters(PlaybackParameters playbackParameters) { throw new IllegalStateException(""Missing implementation to handle COMMAND_SET_SPEED_AND_PITCH""); } /** * Handles calls to {@link Player#setTrackSelectionParameters}. * *

Will only be called if {@link Player#COMMAND_SET_TRACK_SELECTION_PARAMETERS} is available. * * @param trackSelectionParameters The requested {@link TrackSelectionParameters}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetTrackSelectionParameters( TrackSelectionParameters trackSelectionParameters) { throw new IllegalStateException( ""Missing implementation to handle COMMAND_SET_TRACK_SELECTION_PARAMETERS""); } /** * Handles calls to {@link Player#setPlaylistMetadata}. * *

Will only be called if {@link Player#COMMAND_SET_PLAYLIST_METADATA} is available. * * @param playlistMetadata The requested {@linkplain MediaMetadata playlist metadata}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetPlaylistMetadata(MediaMetadata playlistMetadata) { throw new IllegalStateException( ""Missing implementation to handle COMMAND_SET_PLAYLIST_METADATA""); } /** * Handles calls to {@link Player#setVolume}. * *

Will only be called if {@link Player#COMMAND_SET_VOLUME} is available. * * @param volume The requested audio volume, with 0 being silence and 1 being unity gain (signal * unchanged). * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetVolume(@FloatRange(from = 0, to = 1.0) float volume) { throw new IllegalStateException(""Missing implementation to handle COMMAND_SET_VOLUME""); } /** * Handles calls to {@link Player#setDeviceVolume(int)} and {@link Player#setDeviceVolume(int, * int)}. * *

Will only be called if {@link Player#COMMAND_SET_DEVICE_VOLUME} or {@link * Player#COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS} is available. * * @param deviceVolume The requested device volume. * @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetDeviceVolume( @IntRange(from = 0) int deviceVolume, int flags) { throw new IllegalStateException( ""Missing implementation to handle COMMAND_SET_DEVICE_VOLUME or"" + "" COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS""); } /** * Handles calls to {@link Player#increaseDeviceVolume()} and {@link * Player#increaseDeviceVolume(int)}. * *

Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} or {@link * Player#COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is available. * * @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleIncreaseDeviceVolume(@C.VolumeFlags int flags) { throw new IllegalStateException( ""Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME or"" + "" COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS""); } /** * Handles calls to {@link Player#decreaseDeviceVolume()} and {@link * Player#decreaseDeviceVolume(int)}. * *

Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} or {@link * Player#COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is available. * * @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleDecreaseDeviceVolume(@C.VolumeFlags int flags) { throw new IllegalStateException( ""Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME or"" + "" COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS""); } /** * Handles calls to {@link Player#setDeviceMuted(boolean)} and {@link * Player#setDeviceMuted(boolean, int)}. * *

Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} or {@link * Player#COMMAND_ADJUST_DEVICE_VOLUME} is available. * * @param muted Whether the device was requested to be muted. * @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetDeviceMuted(boolean muted, @C.VolumeFlags int flags) { throw new IllegalStateException( ""Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME or"" + "" COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS""); } /** * Handles calls to set the video output. * *

Will only be called if {@link Player#COMMAND_SET_VIDEO_SURFACE} is available. * * @param videoOutput The requested video output. This is either a {@link Surface}, {@link * SurfaceHolder}, {@link TextureView} or {@link SurfaceView}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetVideoOutput(Object videoOutput) { throw new IllegalStateException(""Missing implementation to handle COMMAND_SET_VIDEO_SURFACE""); } /** * Handles calls to clear the video output. * *

Will only be called if {@link Player#COMMAND_SET_VIDEO_SURFACE} is available. * * @param videoOutput The video output to clear. If null any current output should be cleared. If * non-null, the output should only be cleared if it matches the provided argument. This is * either a {@link Surface}, {@link SurfaceHolder}, {@link TextureView} or {@link * SurfaceView}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleClearVideoOutput(@Nullable Object videoOutput) { throw new IllegalStateException(""Missing implementation to handle COMMAND_SET_VIDEO_SURFACE""); } /** * Handles calls to {@link Player#setMediaItem} and {@link Player#setMediaItems}. * *

Will only be called if {@link Player#COMMAND_SET_MEDIA_ITEM} or {@link * Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. If only {@link Player#COMMAND_SET_MEDIA_ITEM} * is available, the list of media items will always contain exactly one item. * * @param mediaItems The media items to add. * @param startIndex The index at which to start playback from, or {@link C#INDEX_UNSET} to start * at the default item. * @param startPositionMs The position in milliseconds to start playback from, or {@link * C#TIME_UNSET} to start at the default position in the media item. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSetMediaItems( List mediaItems, int startIndex, long startPositionMs) { throw new IllegalStateException(""Missing implementation to handle COMMAND_SET_MEDIA_ITEM(S)""); } /** * Handles calls to {@link Player#addMediaItem} and {@link Player#addMediaItems}. * *

Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. * * @param index The index at which to add the items. The index is in the range 0 <= {@code * index} <= {@link #getMediaItemCount()}. * @param mediaItems The media items to add. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleAddMediaItems(int index, List mediaItems) { throw new IllegalStateException(""Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS""); } /** * Handles calls to {@link Player#moveMediaItem} and {@link Player#moveMediaItems}. * *

Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. * * @param fromIndex The start index of the items to move. The index is in the range 0 <= {@code * fromIndex} < {@link #getMediaItemCount()}. * @param toIndex The index of the first item not to be included in the move (exclusive). The * index is in the range {@code fromIndex} < {@code toIndex} <= {@link * #getMediaItemCount()}. * @param newIndex The new index of the first moved item. The index is in the range {@code 0} * <= {@code newIndex} < {@link #getMediaItemCount() - (toIndex - fromIndex)}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleMoveMediaItems(int fromIndex, int toIndex, int newIndex) { throw new IllegalStateException(""Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS""); } /** * Handles calls to {@link Player#replaceMediaItem} and {@link Player#replaceMediaItems}. * *

Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. * * @param fromIndex The start index of the items to replace. The index is in the range 0 <= * {@code fromIndex} < {@link #getMediaItemCount()}. * @param toIndex The index of the first item not to be replaced (exclusive). The index is in the * range {@code fromIndex} < {@code toIndex} <= {@link #getMediaItemCount()}. * @param mediaItems The media items to replace the specified range with. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleReplaceMediaItems( int fromIndex, int toIndex, List mediaItems) { ListenableFuture addFuture = handleAddMediaItems(toIndex, mediaItems); ListenableFuture removeFuture = handleRemoveMediaItems(fromIndex, toIndex); return Util.transformFutureAsync(addFuture, unused -> removeFuture); } /** * Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}. * *

Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available. * * @param fromIndex The index at which to start removing media items. The index is in the range 0 * <= {@code fromIndex} < {@link #getMediaItemCount()}. * @param toIndex The index of the first item to be kept (exclusive). The index is in the range * {@code fromIndex} < {@code toIndex} <= {@link #getMediaItemCount()}. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleRemoveMediaItems(int fromIndex, int toIndex) { throw new IllegalStateException(""Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS""); } /** * Handles calls to {@link Player#seekTo} and other seek operations (for example, {@link * Player#seekToNext}). * *

Will only be called if the appropriate {@link Player.Command}, for example {@link * Player#COMMAND_SEEK_TO_MEDIA_ITEM} or {@link Player#COMMAND_SEEK_TO_NEXT}, is available. * * @param mediaItemIndex The media item index to seek to. The index is in the range 0 <= {@code * mediaItemIndex} < {@code mediaItems.size()}. * @param positionMs The position in milliseconds to start playback from, or {@link C#TIME_UNSET} * to start at the default position in the media item. * @param seekCommand The {@link Player.Command} used to trigger the seek. * @return A {@link ListenableFuture} indicating the completion of all immediate {@link State} * changes caused by this call. */ @ForOverride protected ListenableFuture handleSeek( int mediaItemIndex, long positionMs, @Player.Command int seekCommand) { throw new IllegalStateException(""Missing implementation to handle one of the COMMAND_SEEK_*""); } @RequiresNonNull(""state"") private boolean shouldHandleCommand(@Player.Command int commandCode) { return !released && state.availableCommands.contains(commandCode); } @SuppressWarnings(""deprecation"") // Calling deprecated listener methods. @RequiresNonNull(""state"") private void updateStateAndInformListeners( State newState, boolean seeked, boolean isRepeatingCurrentItem) { State previousState = state; // Assign new state immediately such that all getters return the right values, but use a // snapshot of the previous and new state so that listener invocations are triggered correctly. this.state = newState; if (newState.hasPositionDiscontinuity || newState.newlyRenderedFirstFrame) { // Clear one-time events to avoid signalling them again later. this.state = this.state .buildUpon() .clearPositionDiscontinuity() .setNewlyRenderedFirstFrame(false) .build(); } boolean playWhenReadyChanged = previousState.playWhenReady != newState.playWhenReady; boolean playbackStateChanged = previousState.playbackState != newState.playbackState; Tracks previousTracks = getCurrentTracksInternal(previousState); Tracks newTracks = getCurrentTracksInternal(newState); MediaMetadata previousMediaMetadata = getMediaMetadataInternal(previousState); MediaMetadata newMediaMetadata = getMediaMetadataInternal(newState); int positionDiscontinuityReason = getPositionDiscontinuityReason(previousState, newState, seeked, window, period); boolean timelineChanged = !previousState.timeline.equals(newState.timeline); int mediaItemTransitionReason = getMediaItemTransitionReason( previousState, newState, positionDiscontinuityReason, isRepeatingCurrentItem, window); if (timelineChanged) { @Player.TimelineChangeReason int timelineChangeReason = getTimelineChangeReason(previousState.playlist, newState.playlist); listeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newState.timeline, timelineChangeReason)); } if (positionDiscontinuityReason != C.INDEX_UNSET) { PositionInfo previousPositionInfo = getPositionInfo(previousState, /* useDiscontinuityPosition= */ false, window, period); PositionInfo positionInfo = getPositionInfo( newState, /* useDiscontinuityPosition= */ newState.hasPositionDiscontinuity, window, period); listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, listener -> { listener.onPositionDiscontinuity(positionDiscontinuityReason); listener.onPositionDiscontinuity( previousPositionInfo, positionInfo, positionDiscontinuityReason); }); } if (mediaItemTransitionReason != C.INDEX_UNSET) { @Nullable MediaItem mediaItem = newState.timeline.isEmpty() ? null : newState.playlist.get(getCurrentMediaItemIndexInternal(newState)).mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason)); } if (!Util.areEqual(previousState.playerError, newState.playerError)) { listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerErrorChanged(newState.playerError)); if (newState.playerError != null) { listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(castNonNull(newState.playerError))); } } if (!previousState.trackSelectionParameters.equals(newState.trackSelectionParameters)) { listeners.queueEvent( Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, listener -> listener.onTrackSelectionParametersChanged(newState.trackSelectionParameters)); } if (!previousTracks.equals(newTracks)) { listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newTracks)); } if (!previousMediaMetadata.equals(newMediaMetadata)) { listeners.queueEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(newMediaMetadata)); } if (previousState.isLoading != newState.isLoading) { listeners.queueEvent( Player.EVENT_IS_LOADING_CHANGED, listener -> { listener.onLoadingChanged(newState.isLoading); listener.onIsLoadingChanged(newState.isLoading); }); } if (playWhenReadyChanged || playbackStateChanged) { listeners.queueEvent( /* eventFlag= */ C.INDEX_UNSET, listener -> listener.onPlayerStateChanged(newState.playWhenReady, newState.playbackState)); } if (playbackStateChanged) { listeners.queueEvent( Player.EVENT_PLAYBACK_STATE_CHANGED, listener -> listener.onPlaybackStateChanged(newState.playbackState)); } if (playWhenReadyChanged || previousState.playWhenReadyChangeReason != newState.playWhenReadyChangeReason) { listeners.queueEvent( Player.EVENT_PLAY_WHEN_READY_CHANGED, listener -> listener.onPlayWhenReadyChanged( newState.playWhenReady, newState.playWhenReadyChangeReason)); } if (previousState.playbackSuppressionReason != newState.playbackSuppressionReason) { listeners.queueEvent( Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, listener -> listener.onPlaybackSuppressionReasonChanged(newState.playbackSuppressionReason)); } if (isPlaying(previousState) != isPlaying(newState)) { listeners.queueEvent( Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying(newState))); } if (!previousState.playbackParameters.equals(newState.playbackParameters)) { listeners.queueEvent( Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newState.playbackParameters)); } if (previousState.repeatMode != newState.repeatMode) { listeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(newState.repeatMode)); } if (previousState.shuffleModeEnabled != newState.shuffleModeEnabled) { listeners.queueEvent( Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, listener -> listener.onShuffleModeEnabledChanged(newState.shuffleModeEnabled)); } if (previousState.seekBackIncrementMs != newState.seekBackIncrementMs) { listeners.queueEvent( Player.EVENT_SEEK_BACK_INCREMENT_CHANGED, listener -> listener.onSeekBackIncrementChanged(newState.seekBackIncrementMs)); } if (previousState.seekForwardIncrementMs != newState.seekForwardIncrementMs) { listeners.queueEvent( Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED, listener -> listener.onSeekForwardIncrementChanged(newState.seekForwardIncrementMs)); } if (previousState.maxSeekToPreviousPositionMs != newState.maxSeekToPreviousPositionMs) { listeners.queueEvent( Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, listener -> listener.onMaxSeekToPreviousPositionChanged(newState.maxSeekToPreviousPositionMs)); } if (!previousState.audioAttributes.equals(newState.audioAttributes)) { listeners.queueEvent( Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, listener -> listener.onAudioAttributesChanged(newState.audioAttributes)); } if (!previousState.videoSize.equals(newState.videoSize)) { listeners.queueEvent( Player.EVENT_VIDEO_SIZE_CHANGED, listener -> listener.onVideoSizeChanged(newState.videoSize)); } if (!previousState.deviceInfo.equals(newState.deviceInfo)) { listeners.queueEvent( Player.EVENT_DEVICE_INFO_CHANGED, listener -> listener.onDeviceInfoChanged(newState.deviceInfo)); } if (!previousState.playlistMetadata.equals(newState.playlistMetadata)) { listeners.queueEvent( Player.EVENT_PLAYLIST_METADATA_CHANGED, listener -> listener.onPlaylistMetadataChanged(newState.playlistMetadata)); } if (newState.newlyRenderedFirstFrame) { listeners.queueEvent(Player.EVENT_RENDERED_FIRST_FRAME, Listener::onRenderedFirstFrame); } if (!previousState.surfaceSize.equals(newState.surfaceSize)) { listeners.queueEvent( Player.EVENT_SURFACE_SIZE_CHANGED, listener -> listener.onSurfaceSizeChanged( newState.surfaceSize.getWidth(), newState.surfaceSize.getHeight())); } if (previousState.volume != newState.volume) { listeners.queueEvent( Player.EVENT_VOLUME_CHANGED, listener -> listener.onVolumeChanged(newState.volume)); } if (previousState.deviceVolume != newState.deviceVolume || previousState.isDeviceMuted != newState.isDeviceMuted) { listeners.queueEvent( Player.EVENT_DEVICE_VOLUME_CHANGED, listener -> listener.onDeviceVolumeChanged(newState.deviceVolume, newState.isDeviceMuted)); } if (!previousState.currentCues.equals(newState.currentCues)) { listeners.queueEvent( Player.EVENT_CUES, listener -> { listener.onCues(newState.currentCues.cues); listener.onCues(newState.currentCues); }); } if (!previousState.timedMetadata.equals(newState.timedMetadata) && newState.timedMetadata.presentationTimeUs != C.TIME_UNSET) { listeners.queueEvent( Player.EVENT_METADATA, listener -> listener.onMetadata(newState.timedMetadata)); } if (!previousState.availableCommands.equals(newState.availableCommands)) { listeners.queueEvent( Player.EVENT_AVAILABLE_COMMANDS_CHANGED, listener -> listener.onAvailableCommandsChanged(newState.availableCommands)); } listeners.flushEvents(); } @EnsuresNonNull(""state"") private void verifyApplicationThreadAndInitState() { if (Thread.currentThread() != applicationLooper.getThread()) { String message = Util.formatInvariant( ""Player is accessed on the wrong thread.\n"" + ""Current thread: '%s'\n"" + ""Expected thread: '%s'\n"" + ""See https://developer.android.com/guide/topics/media/issues/"" + ""player-accessed-on-wrong-thread"", Thread.currentThread().getName(), applicationLooper.getThread().getName()); throw new IllegalStateException(message); } if (state == null) { // First time accessing state. state = getState(); } } @RequiresNonNull(""state"") private void updateStateForPendingOperation( ListenableFuture pendingOperation, Supplier placeholderStateSupplier) { updateStateForPendingOperation( pendingOperation, placeholderStateSupplier, /* seeked= */ false, /* isRepeatingCurrentItem= */ false); } @RequiresNonNull(""state"") private void updateStateForPendingOperation( ListenableFuture pendingOperation, Supplier placeholderStateSupplier, boolean seeked, boolean isRepeatingCurrentItem) { if (pendingOperation.isDone() && pendingOperations.isEmpty()) { updateStateAndInformListeners(getState(), seeked, isRepeatingCurrentItem); } else { pendingOperations.add(pendingOperation); State suggestedPlaceholderState = placeholderStateSupplier.get(); updateStateAndInformListeners( getPlaceholderState(suggestedPlaceholderState), seeked, isRepeatingCurrentItem); pendingOperation.addListener( () -> { castNonNull(state); // Already checked by method @RequiresNonNull pre-condition. pendingOperations.remove(pendingOperation); if (pendingOperations.isEmpty() && !released) { updateStateAndInformListeners( getState(), /* seeked= */ false, /* isRepeatingCurrentItem= */ false); } }, this::postOrRunOnApplicationHandler); } } private void postOrRunOnApplicationHandler(Runnable runnable) { if (applicationHandler.getLooper() == Looper.myLooper()) { runnable.run(); } else { applicationHandler.post(runnable); } } private static boolean isPlaying(State state) { return state.playWhenReady && state.playbackState == Player.STATE_READY && state.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE; } private static Tracks getCurrentTracksInternal(State state) { return state.playlist.isEmpty() ? Tracks.EMPTY : state.playlist.get(getCurrentMediaItemIndexInternal(state)).tracks; } private static MediaMetadata getMediaMetadataInternal(State state) { return state.playlist.isEmpty() ? MediaMetadata.EMPTY : state.playlist.get(getCurrentMediaItemIndexInternal(state)).combinedMediaMetadata; } private static int getCurrentMediaItemIndexInternal(State state) { if (state.currentMediaItemIndex != C.INDEX_UNSET) { return state.currentMediaItemIndex; } return 0; // TODO: Use shuffle order to get first item if playlist is not empty. } private static long getContentPositionMsInternal(State state) { return getPositionOrDefaultInMediaItem(state.contentPositionMsSupplier.get(), state); } private static long getContentBufferedPositionMsInternal(State state) { return getPositionOrDefaultInMediaItem(state.contentBufferedPositionMsSupplier.get(), state); } private static long getPositionOrDefaultInMediaItem(long positionMs, State state) { if (positionMs != C.TIME_UNSET) { return positionMs; } if (state.playlist.isEmpty()) { return 0; } return usToMs(state.playlist.get(getCurrentMediaItemIndexInternal(state)).defaultPositionUs); } private static int getCurrentPeriodIndexInternal( State state, Timeline.Window window, Timeline.Period period) { int currentMediaItemIndex = getCurrentMediaItemIndexInternal(state); if (state.timeline.isEmpty()) { return currentMediaItemIndex; } return getPeriodIndexFromWindowPosition( state.timeline, currentMediaItemIndex, getContentPositionMsInternal(state), window, period); } private static int getPeriodIndexFromWindowPosition( Timeline timeline, int windowIndex, long windowPositionMs, Timeline.Window window, Timeline.Period period) { Object periodUid = timeline.getPeriodPositionUs(window, period, windowIndex, msToUs(windowPositionMs)).first; return timeline.getIndexOfPeriod(periodUid); } private static @Player.TimelineChangeReason int getTimelineChangeReason( List previousPlaylist, List newPlaylist) { if (previousPlaylist.size() != newPlaylist.size()) { return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; } for (int i = 0; i < previousPlaylist.size(); i++) { Object previousUid = previousPlaylist.get(i).uid; Object newUid = newPlaylist.get(i).uid; boolean resolvedAutoGeneratedPlaceholder = previousUid instanceof PlaceholderUid && !(newUid instanceof PlaceholderUid); if (!previousUid.equals(newUid) && !resolvedAutoGeneratedPlaceholder) { return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; } } return Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE; } private static int getPositionDiscontinuityReason( State previousState, State newState, boolean seeked, Timeline.Window window, Timeline.Period period) { if (newState.hasPositionDiscontinuity) { // We were asked to report a discontinuity. return newState.positionDiscontinuityReason; } if (seeked) { return Player.DISCONTINUITY_REASON_SEEK; } if (previousState.playlist.isEmpty()) { // First change from an empty playlist is not reported as a discontinuity. return C.INDEX_UNSET; } if (newState.playlist.isEmpty()) { // The playlist became empty. return Player.DISCONTINUITY_REASON_REMOVE; } Object previousPeriodUid = previousState.timeline.getUidOfPeriod( getCurrentPeriodIndexInternal(previousState, window, period)); Object newPeriodUid = newState.timeline.getUidOfPeriod(getCurrentPeriodIndexInternal(newState, window, period)); if (previousPeriodUid instanceof PlaceholderUid && !(newPeriodUid instanceof PlaceholderUid)) { // An auto-generated placeholder was resolved to a real item. return C.INDEX_UNSET; } if (!newPeriodUid.equals(previousPeriodUid) || previousState.currentAdGroupIndex != newState.currentAdGroupIndex || previousState.currentAdIndexInAdGroup != newState.currentAdIndexInAdGroup) { // The current period or ad inside a period changed. if (newState.timeline.getIndexOfPeriod(previousPeriodUid) == C.INDEX_UNSET) { // The previous period no longer exists. return Player.DISCONTINUITY_REASON_REMOVE; } // Check if reached the previous period's or ad's duration to assume an auto-transition. long previousPositionMs = getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period); long previousDurationMs = getPeriodOrAdDurationMs(previousState, previousPeriodUid, period); return previousDurationMs != C.TIME_UNSET && previousPositionMs >= previousDurationMs ? Player.DISCONTINUITY_REASON_AUTO_TRANSITION : Player.DISCONTINUITY_REASON_SKIP; } // We are in the same content period or ad. Check if the position deviates more than a // reasonable threshold from the previous one. long previousPositionMs = getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period); long newPositionMs = getCurrentPeriodOrAdPositionMs(newState, newPeriodUid, period); if (Math.abs(previousPositionMs - newPositionMs) < POSITION_DISCONTINUITY_THRESHOLD_MS) { return C.INDEX_UNSET; } // Check if we previously reached the end of the item to assume an auto-repetition. long previousDurationMs = getPeriodOrAdDurationMs(previousState, previousPeriodUid, period); return previousDurationMs != C.TIME_UNSET && previousPositionMs >= previousDurationMs ? Player.DISCONTINUITY_REASON_AUTO_TRANSITION : Player.DISCONTINUITY_REASON_INTERNAL; } private static long getCurrentPeriodOrAdPositionMs( State state, Object currentPeriodUid, Timeline.Period period) { return state.currentAdGroupIndex != C.INDEX_UNSET ? state.adPositionMsSupplier.get() : getContentPositionMsInternal(state) - state.timeline.getPeriodByUid(currentPeriodUid, period).getPositionInWindowMs(); } private static long getPeriodOrAdDurationMs( State state, Object currentPeriodUid, Timeline.Period period) { state.timeline.getPeriodByUid(currentPeriodUid, period); long periodOrAdDurationUs = state.currentAdGroupIndex == C.INDEX_UNSET ? period.durationUs : period.getAdDurationUs(state.currentAdGroupIndex, state.currentAdIndexInAdGroup); return usToMs(periodOrAdDurationUs); } private static PositionInfo getPositionInfo( State state, boolean useDiscontinuityPosition, Timeline.Window window, Timeline.Period period) { @Nullable Object windowUid = null; @Nullable Object periodUid = null; int mediaItemIndex = getCurrentMediaItemIndexInternal(state); int periodIndex = C.INDEX_UNSET; @Nullable MediaItem mediaItem = null; if (!state.timeline.isEmpty()) { periodIndex = getCurrentPeriodIndexInternal(state, window, period); periodUid = state.timeline.getPeriod(periodIndex, period, /* setIds= */ true).uid; windowUid = state.timeline.getWindow(mediaItemIndex, window).uid; mediaItem = window.mediaItem; } long contentPositionMs; long positionMs; if (useDiscontinuityPosition) { positionMs = state.discontinuityPositionMs; contentPositionMs = state.currentAdGroupIndex == C.INDEX_UNSET ? positionMs : getContentPositionMsInternal(state); } else { contentPositionMs = getContentPositionMsInternal(state); positionMs = state.currentAdGroupIndex != C.INDEX_UNSET ? state.adPositionMsSupplier.get() : contentPositionMs; } return new PositionInfo( windowUid, mediaItemIndex, mediaItem, periodUid, periodIndex, positionMs, contentPositionMs, state.currentAdGroupIndex, state.currentAdIndexInAdGroup); } private static int getMediaItemTransitionReason( State previousState, State newState, int positionDiscontinuityReason, boolean isRepeatingCurrentItem, Timeline.Window window) { Timeline previousTimeline = previousState.timeline; Timeline newTimeline = newState.timeline; if (newTimeline.isEmpty() && previousTimeline.isEmpty()) { return C.INDEX_UNSET; } else if (newTimeline.isEmpty() != previousTimeline.isEmpty()) { return MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; } Object previousWindowUid = previousState.timeline.getWindow(getCurrentMediaItemIndexInternal(previousState), window) .uid; Object newWindowUid = newState.timeline.getWindow(getCurrentMediaItemIndexInternal(newState), window).uid; if (previousWindowUid instanceof PlaceholderUid && !(newWindowUid instanceof PlaceholderUid)) { // An auto-generated placeholder was resolved to a real item. return C.INDEX_UNSET; } if (!previousWindowUid.equals(newWindowUid)) { if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { return MEDIA_ITEM_TRANSITION_REASON_AUTO; } else if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK) { return MEDIA_ITEM_TRANSITION_REASON_SEEK; } else { return MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; } } // Only mark changes within the current item as a transition if we are repeating automatically // or via a seek to next/previous. if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION && getContentPositionMsInternal(previousState) > getContentPositionMsInternal(newState)) { return MEDIA_ITEM_TRANSITION_REASON_REPEAT; } if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK && isRepeatingCurrentItem) { return MEDIA_ITEM_TRANSITION_REASON_SEEK; } return C.INDEX_UNSET; } private static Size getSurfaceHolderSize(SurfaceHolder surfaceHolder) { if (!surfaceHolder.getSurface().isValid()) { return Size.ZERO; } Rect surfaceFrame = surfaceHolder.getSurfaceFrame(); return new Size(surfaceFrame.width(), surfaceFrame.height()); } private static int getMediaItemIndexInNewPlaylist( List oldPlaylist, Timeline newPlaylistTimeline, int oldMediaItemIndex, Timeline.Period period) { if (oldPlaylist.isEmpty()) { return oldMediaItemIndex < newPlaylistTimeline.getWindowCount() ? oldMediaItemIndex : C.INDEX_UNSET; } Object oldFirstPeriodUid = oldPlaylist.get(oldMediaItemIndex).getPeriodUid(/* periodIndexInMediaItem= */ 0); if (newPlaylistTimeline.getIndexOfPeriod(oldFirstPeriodUid) == C.INDEX_UNSET) { return C.INDEX_UNSET; } return newPlaylistTimeline.getPeriodByUid(oldFirstPeriodUid, period).windowIndex; } private static State getStateWithNewPlaylist( State oldState, List newPlaylist, Timeline.Period period) { State.Builder stateBuilder = oldState.buildUpon(); stateBuilder.setPlaylist(newPlaylist); Timeline newTimeline = stateBuilder.timeline; long oldPositionMs = oldState.contentPositionMsSupplier.get(); int oldIndex = getCurrentMediaItemIndexInternal(oldState); int newIndex = getMediaItemIndexInNewPlaylist(oldState.playlist, newTimeline, oldIndex, period); long newPositionMs = newIndex == C.INDEX_UNSET ? C.TIME_UNSET : oldPositionMs; // If the current item no longer exists, try to find a matching subsequent item. for (int i = oldIndex + 1; newIndex == C.INDEX_UNSET && i < oldState.playlist.size(); i++) { // TODO: Use shuffle order to iterate. newIndex = getMediaItemIndexInNewPlaylist( oldState.playlist, newTimeline, /* oldMediaItemIndex= */ i, period); } // If this fails, transition to ENDED state. if (oldState.playbackState != Player.STATE_IDLE && newIndex == C.INDEX_UNSET) { stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false); } return buildStateForNewPosition( stateBuilder, oldState, oldPositionMs, newPlaylist, newIndex, newPositionMs, /* keepAds= */ true); } private static State getStateWithNewPlaylistAndPosition( State oldState, List newPlaylist, int newIndex, long newPositionMs) { State.Builder stateBuilder = oldState.buildUpon(); stateBuilder.setPlaylist(newPlaylist); if (oldState.playbackState != Player.STATE_IDLE) { if (newPlaylist.isEmpty() || (newIndex != C.INDEX_UNSET && newIndex >= newPlaylist.size())) { stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false); } else { stateBuilder.setPlaybackState(Player.STATE_BUFFERING); } } long oldPositionMs = oldState.contentPositionMsSupplier.get(); return buildStateForNewPosition( stateBuilder, oldState, oldPositionMs, newPlaylist, newIndex, newPositionMs, /* keepAds= */ false); } private static State buildStateForNewPosition( State.Builder stateBuilder, State oldState, long oldPositionMs, List newPlaylist, int newIndex, long newPositionMs, boolean keepAds) { // Resolve unset or invalid index and position. oldPositionMs = getPositionOrDefaultInMediaItem(oldPositionMs, oldState); if (!newPlaylist.isEmpty() && (newIndex == C.INDEX_UNSET || newIndex >= newPlaylist.size())) { newIndex = 0; // TODO: Use shuffle order to get first index. newPositionMs = C.TIME_UNSET; } if (!newPlaylist.isEmpty() && newPositionMs == C.TIME_UNSET) { newPositionMs = usToMs(newPlaylist.get(newIndex).defaultPositionUs); } boolean oldOrNewPlaylistEmpty = oldState.playlist.isEmpty() || newPlaylist.isEmpty(); boolean mediaItemChanged = !oldOrNewPlaylistEmpty && !oldState .playlist .get(getCurrentMediaItemIndexInternal(oldState)) .uid .equals(newPlaylist.get(newIndex).uid); if (oldOrNewPlaylistEmpty || mediaItemChanged || newPositionMs < oldPositionMs) { // New item or seeking back. Assume no buffer and no ad playback persists. stateBuilder .setCurrentMediaItemIndex(newIndex) .setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET) .setContentPositionMs(newPositionMs) .setContentBufferedPositionMs(PositionSupplier.getConstant(newPositionMs)) .setTotalBufferedDurationMs(PositionSupplier.ZERO); } else if (newPositionMs == oldPositionMs) { // Unchanged position. Assume ad playback and buffer in current item persists. stateBuilder.setCurrentMediaItemIndex(newIndex); if (oldState.currentAdGroupIndex != C.INDEX_UNSET && keepAds) { stateBuilder.setTotalBufferedDurationMs( PositionSupplier.getConstant( oldState. [MASK] .get() - oldState.adPositionMsSupplier.get())); } else { stateBuilder .setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET) .setTotalBufferedDurationMs( PositionSupplier.getConstant( getContentBufferedPositionMsInternal(oldState) - oldPositionMs)); } } else { // Seeking forward. Assume remaining buffer in current item persist, but no ad playback. long contentBufferedDurationMs = max(getContentBufferedPositionMsInternal(oldState), newPositionMs); long totalBufferedDurationMs = max(0, oldState.totalBufferedDurationMsSupplier.get() - (newPositionMs - oldPositionMs)); stateBuilder .setCurrentMediaItemIndex(newIndex) .setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET) .setContentPositionMs(newPositionMs) .setContentBufferedPositionMs(PositionSupplier.getConstant(contentBufferedDurationMs)) .setTotalBufferedDurationMs(PositionSupplier.getConstant(totalBufferedDurationMs)); } return stateBuilder.build(); } private static final class PlaceholderUid {} } ","adBufferedPositionMsSupplier " "/* * Copyright (C) 2014 The Android Open Source Project * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the ""Classpath"" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.sql; /** * Comprehensive information about the database as a whole. *

* This interface is implemented by driver vendors to let users know the capabilities * of a Database Management System (DBMS) in combination with * the driver based on JDBCTM technology * (""JDBC driver"") that is used with it. Different relational DBMSs often support * different features, implement features in different ways, and use different * data types. In addition, a driver may implement a feature on top of what the * DBMS offers. Information returned by methods in this interface applies * to the capabilities of a particular driver and a particular DBMS working * together. Note that as used in this documentation, the term ""database"" is * used generically to refer to both the driver and DBMS. *

* A user for this interface is commonly a tool that needs to discover how to * deal with the underlying DBMS. This is especially true for applications * that are intended to be used with more than one DBMS. For example, a tool might use the method * getTypeInfo to find out what data types can be used in a * CREATE TABLE statement. Or a user might call the method * supportsCorrelatedSubqueries to see if it is possible to use * a correlated subquery or supportsBatchUpdates to see if it is * possible to use batch updates. *

* Some DatabaseMetaData methods return lists of information * in the form of ResultSet objects. * Regular ResultSet methods, such as * getString and getInt, can be used * to retrieve the data from these ResultSet objects. If * a given form of metadata is not available, an empty ResultSet * will be returned. Additional columns beyond the columns defined to be * returned by the ResultSet object for a given method * can be defined by the JDBC driver vendor and must be accessed * by their column label. *

* Some DatabaseMetaData methods take arguments that are * String patterns. These arguments all have names such as fooPattern. * Within a pattern String, ""%"" means match any substring of 0 or more * characters, and ""_"" means match any one character. Only metadata * entries matching the search pattern are returned. If a search pattern * argument is set to null, that argument's criterion will * be dropped from the search. *

*/ public interface DatabaseMetaData extends Wrapper { //---------------------------------------------------------------------- // First, a variety of minor information about the target database. /** * Retrieves whether the current user can call all the procedures * returned by the method getProcedures. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean allProceduresAreCallable() throws SQLException; /** * Retrieves whether the current user can use all the tables returned * by the method getTables in a SELECT * statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean allTablesAreSelectable() throws SQLException; /** * Retrieves the URL for this DBMS. * * @return the URL for this DBMS or null if it cannot be * generated * @exception SQLException if a database access error occurs */ String getURL() throws SQLException; /** * Retrieves the user name as known to this database. * * @return the database user name * @exception SQLException if a database access error occurs */ String getUserName() throws SQLException; /** * Retrieves whether this database is in read-only mode. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean isReadOnly() throws SQLException; /** * Retrieves whether NULL values are sorted high. * Sorted high means that NULL values * sort higher than any other value in a domain. In an ascending order, * if this method returns true, NULL values * will appear at the end. By contrast, the method * nullsAreSortedAtEnd indicates whether NULL values * are sorted at the end regardless of sort order. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean nullsAreSortedHigh() throws SQLException; /** * Retrieves whether NULL values are sorted low. * Sorted low means that NULL values * sort lower than any other value in a domain. In an ascending order, * if this method returns true, NULL values * will appear at the beginning. By contrast, the method * nullsAreSortedAtStart indicates whether NULL values * are sorted at the beginning regardless of sort order. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean nullsAreSortedLow() throws SQLException; /** * Retrieves whether NULL values are sorted at the start regardless * of sort order. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean nullsAreSortedAtStart() throws SQLException; /** * Retrieves whether NULL values are sorted at the end regardless of * sort order. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean nullsAreSortedAtEnd() throws SQLException; /** * Retrieves the name of this database product. * * @return database product name * @exception SQLException if a database access error occurs */ String getDatabaseProductName() throws SQLException; /** * Retrieves the version number of this database product. * * @return database version number * @exception SQLException if a database access error occurs */ String getDatabaseProductVersion() throws SQLException; /** * Retrieves the name of this JDBC driver. * * @return JDBC driver name * @exception SQLException if a database access error occurs */ String getDriverName() throws SQLException; /** * Retrieves the version number of this JDBC driver as a String. * * @return JDBC driver version * @exception SQLException if a database access error occurs */ String getDriverVersion() throws SQLException; /** * Retrieves this JDBC driver's major version number. * * @return JDBC driver major version */ int getDriverMajorVersion(); /** * Retrieves this JDBC driver's minor version number. * * @return JDBC driver minor version number */ int getDriverMinorVersion(); /** * Retrieves whether this database stores tables in a local file. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean usesLocalFiles() throws SQLException; /** * Retrieves whether this database uses a file for each table. * * @return true if this database uses a local file for each table; * false otherwise * @exception SQLException if a database access error occurs */ boolean usesLocalFilePerTable() throws SQLException; /** * Retrieves whether this database treats mixed case unquoted SQL identifiers as * case sensitive and as a result stores them in mixed case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsMixedCaseIdentifiers() throws SQLException; /** * Retrieves whether this database treats mixed case unquoted SQL identifiers as * case insensitive and stores them in upper case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean storesUpperCaseIdentifiers() throws SQLException; /** * Retrieves whether this database treats mixed case unquoted SQL identifiers as * case insensitive and stores them in lower case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean storesLowerCaseIdentifiers() throws SQLException; /** * Retrieves whether this database treats mixed case unquoted SQL identifiers as * case insensitive and stores them in mixed case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean storesMixedCaseIdentifiers() throws SQLException; /** * Retrieves whether this database treats mixed case quoted SQL identifiers as * case sensitive and as a result stores them in mixed case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsMixedCaseQuotedIdentifiers() throws SQLException; /** * Retrieves whether this database treats mixed case quoted SQL identifiers as * case insensitive and stores them in upper case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean storesUpperCaseQuotedIdentifiers() throws SQLException; /** * Retrieves whether this database treats mixed case quoted SQL identifiers as * case insensitive and stores them in lower case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean storesLowerCaseQuotedIdentifiers() throws SQLException; /** * Retrieves whether this database treats mixed case quoted SQL identifiers as * case insensitive and stores them in mixed case. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean storesMixedCaseQuotedIdentifiers() throws SQLException; /** * Retrieves the string used to quote SQL identifiers. * This method returns a space "" "" if identifier quoting is not supported. * * @return the quoting string or a space if quoting is not supported * @exception SQLException if a database access error occurs */ String getIdentifierQuoteString() throws SQLException; /** * Retrieves a comma-separated list of all of this database's SQL keywords * that are NOT also SQL:2003 keywords. * * @return the list of this database's keywords that are not also * SQL:2003 keywords * @exception SQLException if a database access error occurs */ String getSQLKeywords() throws SQLException; /** * Retrieves a comma-separated list of math functions available with * this database. These are the Open /Open CLI math function names used in * the JDBC function escape clause. * * @return the list of math functions supported by this database * @exception SQLException if a database access error occurs */ String getNumericFunctions() throws SQLException; /** * Retrieves a comma-separated list of string functions available with * this database. These are the Open Group CLI string function names used * in the JDBC function escape clause. * * @return the list of string functions supported by this database * @exception SQLException if a database access error occurs */ String getStringFunctions() throws SQLException; /** * Retrieves a comma-separated list of system functions available with * this database. These are the Open Group CLI system function names used * in the JDBC function escape clause. * * @return a list of system functions supported by this database * @exception SQLException if a database access error occurs */ String getSystemFunctions() throws SQLException; /** * Retrieves a comma-separated list of the time and date functions available * with this database. * * @return the list of time and date functions supported by this database * @exception SQLException if a database access error occurs */ String getTimeDateFunctions() throws SQLException; /** * Retrieves the string that can be used to escape wildcard characters. * This is the string that can be used to escape '_' or '%' in * the catalog search parameters that are a pattern (and therefore use one * of the wildcard characters). * *

The '_' character represents any single character; * the '%' character represents any sequence of zero or * more characters. * * @return the string used to escape wildcard characters * @exception SQLException if a database access error occurs */ String getSearchStringEscape() throws SQLException; /** * Retrieves all the ""extra"" characters that can be used in unquoted * identifier names (those beyond a-z, A-Z, 0-9 and _). * * @return the string containing the extra characters * @exception SQLException if a database access error occurs */ String getExtraNameCharacters() throws SQLException; //-------------------------------------------------------------------- // Functions describing which features are supported. /** * Retrieves whether this database supports ALTER TABLE * with add column. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsAlterTableWithAddColumn() throws SQLException; /** * Retrieves whether this database supports ALTER TABLE * with drop column. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsAlterTableWithDropColumn() throws SQLException; /** * Retrieves whether this database supports column aliasing. * *

If so, the SQL AS clause can be used to provide names for * computed columns or to provide alias names for columns as * required. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsColumnAliasing() throws SQLException; /** * Retrieves whether this database supports concatenations between * NULL and non-NULL values being * NULL. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean nullPlusNonNullIsNull() throws SQLException; /** * Retrieves whether this database supports the JDBC scalar function * CONVERT for the conversion of one JDBC type to another. * The JDBC types are the generic SQL data types defined * in java.sql.Types. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsConvert() throws SQLException; /** * Retrieves whether this database supports the JDBC scalar function * CONVERT for conversions between the JDBC types fromType * and toType. The JDBC types are the generic SQL data types defined * in java.sql.Types. * * @param fromType the type to convert from; one of the type codes from * the class java.sql.Types * @param toType the type to convert to; one of the type codes from * the class java.sql.Types * @return true if so; false otherwise * @exception SQLException if a database access error occurs * @see Types */ boolean supportsConvert(int fromType, int toType) throws SQLException; /** * Retrieves whether this database supports table correlation names. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsTableCorrelationNames() throws SQLException; /** * Retrieves whether, when table correlation names are supported, they * are restricted to being different from the names of the tables. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsDifferentTableCorrelationNames() throws SQLException; /** * Retrieves whether this database supports expressions in * ORDER BY lists. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsExpressionsInOrderBy() throws SQLException; /** * Retrieves whether this database supports using a column that is * not in the SELECT statement in an * ORDER BY clause. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsOrderByUnrelated() throws SQLException; /** * Retrieves whether this database supports some form of * GROUP BY clause. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsGroupBy() throws SQLException; /** * Retrieves whether this database supports using a column that is * not in the SELECT statement in a * GROUP BY clause. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsGroupByUnrelated() throws SQLException; /** * Retrieves whether this database supports using columns not included in * the SELECT statement in a GROUP BY clause * provided that all of the columns in the SELECT statement * are included in the GROUP BY clause. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsGroupByBeyondSelect() throws SQLException; /** * Retrieves whether this database supports specifying a * LIKE escape clause. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsLikeEscapeClause() throws SQLException; /** * Retrieves whether this database supports getting multiple * ResultSet objects from a single call to the * method execute. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsMultipleResultSets() throws SQLException; /** * Retrieves whether this database allows having multiple * transactions open at once (on different connections). * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsMultipleTransactions() throws SQLException; /** * Retrieves whether columns in this database may be defined as non-nullable. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsNonNullableColumns() throws SQLException; /** * Retrieves whether this database supports the ODBC Minimum SQL grammar. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsMinimumSQLGrammar() throws SQLException; /** * Retrieves whether this database supports the ODBC Core SQL grammar. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsCoreSQLGrammar() throws SQLException; /** * Retrieves whether this database supports the ODBC Extended SQL grammar. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsExtendedSQLGrammar() throws SQLException; /** * Retrieves whether this database supports the ANSI92 entry level SQL * grammar. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsANSI92EntryLevelSQL() throws SQLException; /** * Retrieves whether this database supports the ANSI92 intermediate SQL grammar supported. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsANSI92IntermediateSQL() throws SQLException; /** * Retrieves whether this database supports the ANSI92 full SQL grammar supported. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsANSI92FullSQL() throws SQLException; /** * Retrieves whether this database supports the SQL Integrity * Enhancement Facility. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsIntegrityEnhancementFacility() throws SQLException; /** * Retrieves whether this database supports some form of outer join. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsOuterJoins() throws SQLException; /** * Retrieves whether this database supports full nested outer joins. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsFullOuterJoins() throws SQLException; /** * Retrieves whether this database provides limited support for outer * joins. (This will be true if the method * supportsFullOuterJoins returns true). * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsLimitedOuterJoins() throws SQLException; /** * Retrieves the database vendor's preferred term for ""schema"". * * @return the vendor term for ""schema"" * @exception SQLException if a database access error occurs */ String getSchemaTerm() throws SQLException; /** * Retrieves the database vendor's preferred term for ""procedure"". * * @return the vendor term for ""procedure"" * @exception SQLException if a database access error occurs */ String getProcedureTerm() throws SQLException; /** * Retrieves the database vendor's preferred term for ""catalog"". * * @return the vendor term for ""catalog"" * @exception SQLException if a database access error occurs */ String getCatalogTerm() throws SQLException; /** * Retrieves whether a catalog appears at the start of a fully qualified * table name. If not, the catalog appears at the end. * * @return true if the catalog name appears at the beginning * of a fully qualified table name; false otherwise * @exception SQLException if a database access error occurs */ boolean isCatalogAtStart() throws SQLException; /** * Retrieves the String that this database uses as the * separator between a catalog and table name. * * @return the separator string * @exception SQLException if a database access error occurs */ String getCatalogSeparator() throws SQLException; /** * Retrieves whether a schema name can be used in a data manipulation statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSchemasInDataManipulation() throws SQLException; /** * Retrieves whether a schema name can be used in a procedure call statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSchemasInProcedureCalls() throws SQLException; /** * Retrieves whether a schema name can be used in a table definition statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSchemasInTableDefinitions() throws SQLException; /** * Retrieves whether a schema name can be used in an index definition statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSchemasInIndexDefinitions() throws SQLException; /** * Retrieves whether a schema name can be used in a privilege definition statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSchemasInPrivilegeDefinitions() throws SQLException; /** * Retrieves whether a catalog name can be used in a data manipulation statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsCatalogsInDataManipulation() throws SQLException; /** * Retrieves whether a catalog name can be used in a procedure call statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsCatalogsInProcedureCalls() throws SQLException; /** * Retrieves whether a catalog name can be used in a table definition statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsCatalogsInTableDefinitions() throws SQLException; /** * Retrieves whether a catalog name can be used in an index definition statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsCatalogsInIndexDefinitions() throws SQLException; /** * Retrieves whether a catalog name can be used in a privilege definition statement. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException; /** * Retrieves whether this database supports positioned DELETE * statements. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsPositionedDelete() throws SQLException; /** * Retrieves whether this database supports positioned UPDATE * statements. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsPositionedUpdate() throws SQLException; /** * Retrieves whether this database supports SELECT FOR UPDATE * statements. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSelectForUpdate() throws SQLException; /** * Retrieves whether this database supports stored procedure calls * that use the stored procedure escape syntax. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsStoredProcedures() throws SQLException; /** * Retrieves whether this database supports subqueries in comparison * expressions. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSubqueriesInComparisons() throws SQLException; /** * Retrieves whether this database supports subqueries in * EXISTS expressions. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSubqueriesInExists() throws SQLException; /** * Retrieves whether this database supports subqueries in * IN expressions. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSubqueriesInIns() throws SQLException; /** * Retrieves whether this database supports subqueries in quantified * expressions. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsSubqueriesInQuantifieds() throws SQLException; /** * Retrieves whether this database supports correlated subqueries. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsCorrelatedSubqueries() throws SQLException; /** * Retrieves whether this database supports SQL UNION. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsUnion() throws SQLException; /** * Retrieves whether this database supports SQL UNION ALL. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsUnionAll() throws SQLException; /** * Retrieves whether this database supports keeping cursors open * across commits. * * @return true if cursors always remain open; * false if they might not remain open * @exception SQLException if a database access error occurs */ boolean supportsOpenCursorsAcrossCommit() throws SQLException; /** * Retrieves whether this database supports keeping cursors open * across rollbacks. * * @return true if cursors always remain open; * false if they might not remain open * @exception SQLException if a database access error occurs */ boolean supportsOpenCursorsAcrossRollback() throws SQLException; /** * Retrieves whether this database supports keeping statements open * across commits. * * @return true if statements always remain open; * false if they might not remain open * @exception SQLException if a database access error occurs */ boolean supportsOpenStatementsAcrossCommit() throws SQLException; /** * Retrieves whether this database supports keeping statements open * across rollbacks. * * @return true if statements always remain open; * false if they might not remain open * @exception SQLException if a database access error occurs */ boolean supportsOpenStatementsAcrossRollback() throws SQLException; //---------------------------------------------------------------------- // The following group of methods exposes various limitations // based on the target database with the current driver. // Unless otherwise specified, a result of zero means there is no // limit, or the limit is not known. /** * Retrieves the maximum number of hex characters this database allows in an * inline binary literal. * * @return max the maximum length (in hex characters) for a binary literal; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxBinaryLiteralLength() throws SQLException; /** * Retrieves the maximum number of characters this database allows * for a character literal. * * @return the maximum number of characters allowed for a character literal; * a result of zero means that there is no limit or the limit is * not known * @exception SQLException if a database access error occurs */ int getMaxCharLiteralLength() throws SQLException; /** * Retrieves the maximum number of characters this database allows * for a column name. * * @return the maximum number of characters allowed for a column name; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxColumnNameLength() throws SQLException; /** * Retrieves the maximum number of columns this database allows in a * GROUP BY clause. * * @return the maximum number of columns allowed; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxColumnsInGroupBy() throws SQLException; /** * Retrieves the maximum number of columns this database allows in an index. * * @return the maximum number of columns allowed; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxColumnsInIndex() throws SQLException; /** * Retrieves the maximum number of columns this database allows in an * ORDER BY clause. * * @return the maximum number of columns allowed; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxColumnsInOrderBy() throws SQLException; /** * Retrieves the maximum number of columns this database allows in a * SELECT list. * * @return the maximum number of columns allowed; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxColumnsInSelect() throws SQLException; /** * Retrieves the maximum number of columns this database allows in a table. * * @return the maximum number of columns allowed; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxColumnsInTable() throws SQLException; /** * Retrieves the maximum number of concurrent connections to this * database that are possible. * * @return the maximum number of active connections possible at one time; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxConnections() throws SQLException; /** * Retrieves the maximum number of characters that this database allows in a * cursor name. * * @return the maximum number of characters allowed in a cursor name; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxCursorNameLength() throws SQLException; /** * Retrieves the maximum number of bytes this database allows for an * index, including all of the parts of the index. * * @return the maximum number of bytes allowed; this limit includes the * composite of all the constituent parts of the index; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxIndexLength() throws SQLException; /** * Retrieves the maximum number of characters that this database allows in a * schema name. * * @return the maximum number of characters allowed in a schema name; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxSchemaNameLength() throws SQLException; /** * Retrieves the maximum number of characters that this database allows in a * procedure name. * * @return the maximum number of characters allowed in a procedure name; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxProcedureNameLength() throws SQLException; /** * Retrieves the maximum number of characters that this database allows in a * catalog name. * * @return the maximum number of characters allowed in a catalog name; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxCatalogNameLength() throws SQLException; /** * Retrieves the maximum number of bytes this database allows in * a single row. * * @return the maximum number of bytes allowed for a row; a result of * zero means that there is no limit or the limit is not known * @exception SQLException if a database access error occurs */ int getMaxRowSize() throws SQLException; /** * Retrieves whether the return value for the method * getMaxRowSize includes the SQL data types * LONGVARCHAR and LONGVARBINARY. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean doesMaxRowSizeIncludeBlobs() throws SQLException; /** * Retrieves the maximum number of characters this database allows in * an SQL statement. * * @return the maximum number of characters allowed for an SQL statement; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxStatementLength() throws SQLException; /** * Retrieves the maximum number of active statements to this database * that can be open at the same time. * * @return the maximum number of statements that can be open at one time; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxStatements() throws SQLException; /** * Retrieves the maximum number of characters this database allows in * a table name. * * @return the maximum number of characters allowed for a table name; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxTableNameLength() throws SQLException; /** * Retrieves the maximum number of tables this database allows in a * SELECT statement. * * @return the maximum number of tables allowed in a SELECT * statement; a result of zero means that there is no limit or * the limit is not known * @exception SQLException if a database access error occurs */ int getMaxTablesInSelect() throws SQLException; /** * Retrieves the maximum number of characters this database allows in * a user name. * * @return the maximum number of characters allowed for a user name; * a result of zero means that there is no limit or the limit * is not known * @exception SQLException if a database access error occurs */ int getMaxUserNameLength() throws SQLException; //---------------------------------------------------------------------- /** * Retrieves this database's default transaction isolation level. The * possible values are defined in java.sql.Connection. * * @return the default isolation level * @exception SQLException if a database access error occurs * @see Connection */ int getDefaultTransactionIsolation() throws SQLException; /** * Retrieves whether this database supports transactions. If not, invoking the * method commit is a noop, and the isolation level is * TRANSACTION_NONE. * * @return true if transactions are supported; * false otherwise * @exception SQLException if a database access error occurs */ boolean supportsTransactions() throws SQLException; /** * Retrieves whether this database supports the given transaction isolation level. * * @param level one of the transaction isolation levels defined in * java.sql.Connection * @return true if so; false otherwise * @exception SQLException if a database access error occurs * @see Connection */ boolean supportsTransactionIsolationLevel(int level) throws SQLException; /** * Retrieves whether this database supports both data definition and * data manipulation statements within a transaction. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException; /** * Retrieves whether this database supports only data manipulation * statements within a transaction. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean supportsDataManipulationTransactionsOnly() throws SQLException; /** * Retrieves whether a data definition statement within a transaction forces * the transaction to commit. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean dataDefinitionCausesTransactionCommit() throws SQLException; /** * Retrieves whether this database ignores a data definition statement * within a transaction. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs */ boolean dataDefinitionIgnoredInTransactions() throws SQLException; /** * Retrieves a description of the stored procedures available in the given * catalog. *

* Only procedure descriptions matching the schema and * procedure name criteria are returned. They are ordered by * PROCEDURE_CAT, PROCEDURE_SCHEM, * PROCEDURE_NAME and SPECIFIC_ NAME. * *

Each procedure description has the the following columns: *

    *
  1. PROCEDURE_CAT String => procedure catalog (may be null) *
  2. PROCEDURE_SCHEM String => procedure schema (may be null) *
  3. PROCEDURE_NAME String => procedure name *
  4. reserved for future use *
  5. reserved for future use *
  6. reserved for future use *
  7. REMARKS String => explanatory comment on the procedure *
  8. PROCEDURE_TYPE short => kind of procedure: *
      *
    • procedureResultUnknown - Cannot determine if a return value * will be returned *
    • procedureNoResult - Does not return a return value *
    • procedureReturnsResult - Returns a return value *
    *
  9. SPECIFIC_NAME String => The name which uniquely identifies this * procedure within its schema. *
*

* A user may not have permissions to execute any of the procedures that are * returned by getProcedures * * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param [MASK] a procedure name pattern; must match the * procedure name as it is stored in the database * @return ResultSet - each row is a procedure description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape */ ResultSet getProcedures(String catalog, String schemaPattern, String [MASK] ) throws SQLException; /** * Indicates that it is not known whether the procedure returns * a result. *

* A possible value for column PROCEDURE_TYPE in the * ResultSet object returned by the method * getProcedures. */ int procedureResultUnknown = 0; /** * Indicates that the procedure does not return a result. *

* A possible value for column PROCEDURE_TYPE in the * ResultSet object returned by the method * getProcedures. */ int procedureNoResult = 1; /** * Indicates that the procedure returns a result. *

* A possible value for column PROCEDURE_TYPE in the * ResultSet object returned by the method * getProcedures. */ int procedureReturnsResult = 2; /** * Retrieves a description of the given catalog's stored procedure parameter * and result columns. * *

Only descriptions matching the schema, procedure and * parameter name criteria are returned. They are ordered by * PROCEDURE_CAT, PROCEDURE_SCHEM, PROCEDURE_NAME and SPECIFIC_NAME. Within this, the return value, * if any, is first. Next are the parameter descriptions in call * order. The column descriptions follow in column number order. * *

Each row in the ResultSet is a parameter description or * column description with the following fields: *

    *
  1. PROCEDURE_CAT String => procedure catalog (may be null) *
  2. PROCEDURE_SCHEM String => procedure schema (may be null) *
  3. PROCEDURE_NAME String => procedure name *
  4. COLUMN_NAME String => column/parameter name *
  5. COLUMN_TYPE Short => kind of column/parameter: *
      *
    • procedureColumnUnknown - nobody knows *
    • procedureColumnIn - IN parameter *
    • procedureColumnInOut - INOUT parameter *
    • procedureColumnOut - OUT parameter *
    • procedureColumnReturn - procedure return value *
    • procedureColumnResult - result column in ResultSet *
    *
  6. DATA_TYPE int => SQL type from java.sql.Types *
  7. TYPE_NAME String => SQL type name, for a UDT type the * type name is fully qualified *
  8. PRECISION int => precision *
  9. LENGTH int => length in bytes of data *
  10. SCALE short => scale - null is returned for data types where * SCALE is not applicable. *
  11. RADIX short => radix *
  12. NULLABLE short => can it contain NULL. *
      *
    • procedureNoNulls - does not allow NULL values *
    • procedureNullable - allows NULL values *
    • procedureNullableUnknown - nullability unknown *
    *
  13. REMARKS String => comment describing parameter/column *
  14. COLUMN_DEF String => default value for the column, which should be interpreted as a string when the value is enclosed in single quotes (may be null) *
      *
    • The string NULL (not enclosed in quotes) - if NULL was specified as the default value *
    • TRUNCATE (not enclosed in quotes) - if the specified default value cannot be represented without truncation *
    • NULL - if a default value was not specified *
    *
  15. SQL_DATA_TYPE int => reserved for future use *
  16. SQL_DATETIME_SUB int => reserved for future use *
  17. CHAR_OCTET_LENGTH int => the maximum length of binary and character based columns. For any other datatype the returned value is a * NULL *
  18. ORDINAL_POSITION int => the ordinal position, starting from 1, for the input and output parameters for a procedure. A value of 0 *is returned if this row describes the procedure's return value. For result set columns, it is the *ordinal position of the column in the result set starting from 1. If there are *multiple result sets, the column ordinal positions are implementation * defined. *
  19. IS_NULLABLE String => ISO rules are used to determine the nullability for a column. *
      *
    • YES --- if the column can include NULLs *
    • NO --- if the column cannot include NULLs *
    • empty string --- if the nullability for the * column is unknown *
    *
  20. SPECIFIC_NAME String => the name which uniquely identifies this procedure within its schema. *
* *

Note: Some databases may not return the column * descriptions for a procedure. * *

The PRECISION column represents the specified column size for the given column. * For numeric data, this is the maximum precision. For character data, this is the length in characters. * For datetime datatypes, this is the length in characters of the String representation (assuming the * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype, * this is the length in bytes. Null is returned for data types where the * column size is not applicable. * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param [MASK] a procedure name pattern; must match the * procedure name as it is stored in the database * @param columnNamePattern a column name pattern; must match the column name * as it is stored in the database * @return ResultSet - each row describes a stored procedure parameter or * column * @exception SQLException if a database access error occurs * @see #getSearchStringEscape */ ResultSet getProcedureColumns(String catalog, String schemaPattern, String [MASK] , String columnNamePattern) throws SQLException; /** * Indicates that type of the column is unknown. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getProcedureColumns. */ int procedureColumnUnknown = 0; /** * Indicates that the column stores IN parameters. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getProcedureColumns. */ int procedureColumnIn = 1; /** * Indicates that the column stores INOUT parameters. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getProcedureColumns. */ int procedureColumnInOut = 2; /** * Indicates that the column stores OUT parameters. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getProcedureColumns. */ int procedureColumnOut = 4; /** * Indicates that the column stores return values. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getProcedureColumns. */ int procedureColumnReturn = 5; /** * Indicates that the column stores results. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getProcedureColumns. */ int procedureColumnResult = 3; /** * Indicates that NULL values are not allowed. *

* A possible value for the column * NULLABLE * in the ResultSet object * returned by the method getProcedureColumns. */ int procedureNoNulls = 0; /** * Indicates that NULL values are allowed. *

* A possible value for the column * NULLABLE * in the ResultSet object * returned by the method getProcedureColumns. */ int procedureNullable = 1; /** * Indicates that whether NULL values are allowed * is unknown. *

* A possible value for the column * NULLABLE * in the ResultSet object * returned by the method getProcedureColumns. */ int procedureNullableUnknown = 2; /** * Retrieves a description of the tables available in the given catalog. * Only table descriptions matching the catalog, schema, table * name and type criteria are returned. They are ordered by * TABLE_TYPE, TABLE_CAT, * TABLE_SCHEM and TABLE_NAME. *

* Each table description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. TABLE_TYPE String => table type. Typical types are ""TABLE"", * ""VIEW"", ""SYSTEM TABLE"", ""GLOBAL TEMPORARY"", * ""LOCAL TEMPORARY"", ""ALIAS"", ""SYNONYM"". *
  5. REMARKS String => explanatory comment on the table *
  6. TYPE_CAT String => the types catalog (may be null) *
  7. TYPE_SCHEM String => the types schema (may be null) *
  8. TYPE_NAME String => type name (may be null) *
  9. SELF_REFERENCING_COL_NAME String => name of the designated * ""identifier"" column of a typed table (may be null) *
  10. REF_GENERATION String => specifies how values in * SELF_REFERENCING_COL_NAME are created. Values are * ""SYSTEM"", ""USER"", ""DERIVED"". (may be null) *
* *

Note: Some databases may not return information for * all tables. * * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param tableNamePattern a table name pattern; must match the * table name as it is stored in the database * @param types a list of table types, which must be from the list of table types * returned from {@link #getTableTypes},to include; null returns * all types * @return ResultSet - each row is a table description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape */ ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException; /** * Retrieves the schema names available in this database. The results * are ordered by TABLE_CATALOG and * TABLE_SCHEM. * *

The schema columns are: *

    *
  1. TABLE_SCHEM String => schema name *
  2. TABLE_CATALOG String => catalog name (may be null) *
* * @return a ResultSet object in which each row is a * schema description * @exception SQLException if a database access error occurs * */ ResultSet getSchemas() throws SQLException; /** * Retrieves the catalog names available in this database. The results * are ordered by catalog name. * *

The catalog column is: *

    *
  1. TABLE_CAT String => catalog name *
* * @return a ResultSet object in which each row has a * single String column that is a catalog name * @exception SQLException if a database access error occurs */ ResultSet getCatalogs() throws SQLException; /** * Retrieves the table types available in this database. The results * are ordered by table type. * *

The table type is: *

    *
  1. TABLE_TYPE String => table type. Typical types are ""TABLE"", * ""VIEW"", ""SYSTEM TABLE"", ""GLOBAL TEMPORARY"", * ""LOCAL TEMPORARY"", ""ALIAS"", ""SYNONYM"". *
* * @return a ResultSet object in which each row has a * single String column that is a table type * @exception SQLException if a database access error occurs */ ResultSet getTableTypes() throws SQLException; /** * Retrieves a description of table columns available in * the specified catalog. * *

Only column descriptions matching the catalog, schema, table * and column name criteria are returned. They are ordered by * TABLE_CAT,TABLE_SCHEM, * TABLE_NAME, and ORDINAL_POSITION. * *

Each column description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. DATA_TYPE int => SQL type from java.sql.Types *
  6. TYPE_NAME String => Data source dependent type name, * for a UDT the type name is fully qualified *
  7. COLUMN_SIZE int => column size. *
  8. BUFFER_LENGTH is not used. *
  9. DECIMAL_DIGITS int => the number of fractional digits. Null is returned for data types where * DECIMAL_DIGITS is not applicable. *
  10. NUM_PREC_RADIX int => Radix (typically either 10 or 2) *
  11. NULLABLE int => is NULL allowed. *
      *
    • columnNoNulls - might not allow NULL values *
    • columnNullable - definitely allows NULL values *
    • columnNullableUnknown - nullability unknown *
    *
  12. REMARKS String => comment describing column (may be null) *
  13. COLUMN_DEF String => default value for the column, which should be interpreted as a string when the value is enclosed in single quotes (may be null) *
  14. SQL_DATA_TYPE int => unused *
  15. SQL_DATETIME_SUB int => unused *
  16. CHAR_OCTET_LENGTH int => for char types the * maximum number of bytes in the column *
  17. ORDINAL_POSITION int => index of column in table * (starting at 1) *
  18. IS_NULLABLE String => ISO rules are used to determine the nullability for a column. *
      *
    • YES --- if the column can include NULLs *
    • NO --- if the column cannot include NULLs *
    • empty string --- if the nullability for the * column is unknown *
    *
  19. SCOPE_CATALOG String => catalog of table that is the scope * of a reference attribute (null if DATA_TYPE isn't REF) *
  20. SCOPE_SCHEMA String => schema of table that is the scope * of a reference attribute (null if the DATA_TYPE isn't REF) *
  21. SCOPE_TABLE String => table name that this the scope * of a reference attribute (null if the DATA_TYPE isn't REF) *
  22. SOURCE_DATA_TYPE short => source type of a distinct type or user-generated * Ref type, SQL type from java.sql.Types (null if DATA_TYPE * isn't DISTINCT or user-generated REF) *
  23. IS_AUTOINCREMENT String => Indicates whether this column is auto incremented *
      *
    • YES --- if the column is auto incremented *
    • NO --- if the column is not auto incremented *
    • empty string --- if it cannot be determined whether the column is auto incremented *
    *
  24. IS_GENERATEDCOLUMN String => Indicates whether this is a generated column *
      *
    • YES --- if this a generated column *
    • NO --- if this not a generated column *
    • empty string --- if it cannot be determined whether this is a generated column *
    *
* *

The COLUMN_SIZE column specifies the column size for the given column. * For numeric data, this is the maximum precision. For character data, this is the length in characters. * For datetime datatypes, this is the length in characters of the String representation (assuming the * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype, * this is the length in bytes. Null is returned for data types where the * column size is not applicable. * * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param tableNamePattern a table name pattern; must match the * table name as it is stored in the database * @param columnNamePattern a column name pattern; must match the column * name as it is stored in the database * @return ResultSet - each row is a column description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape */ ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException; /** * Indicates that the column might not allow NULL values. *

* A possible value for the column * NULLABLE * in the ResultSet returned by the method * getColumns. */ int columnNoNulls = 0; /** * Indicates that the column definitely allows NULL values. *

* A possible value for the column * NULLABLE * in the ResultSet returned by the method * getColumns. */ int columnNullable = 1; /** * Indicates that the nullability of columns is unknown. *

* A possible value for the column * NULLABLE * in the ResultSet returned by the method * getColumns. */ int columnNullableUnknown = 2; /** * Retrieves a description of the access rights for a table's columns. * *

Only privileges matching the column name criteria are * returned. They are ordered by COLUMN_NAME and PRIVILEGE. * *

Each privilige description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. GRANTOR String => grantor of access (may be null) *
  6. GRANTEE String => grantee of access *
  7. PRIVILEGE String => name of access (SELECT, * INSERT, UPDATE, REFRENCES, ...) *
  8. IS_GRANTABLE String => ""YES"" if grantee is permitted * to grant to others; ""NO"" if not; null if unknown *
* * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schema a schema name; must match the schema name as it is * stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param table a table name; must match the table name as it is * stored in the database * @param columnNamePattern a column name pattern; must match the column * name as it is stored in the database * @return ResultSet - each row is a column privilege description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape */ ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException; /** * Retrieves a description of the access rights for each table available * in a catalog. Note that a table privilege applies to one or * more columns in the table. It would be wrong to assume that * this privilege applies to all columns (this may be true for * some systems but is not true for all.) * *

Only privileges matching the schema and table name * criteria are returned. They are ordered by * TABLE_CAT, * TABLE_SCHEM, TABLE_NAME, * and PRIVILEGE. * *

Each privilige description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. GRANTOR String => grantor of access (may be null) *
  5. GRANTEE String => grantee of access *
  6. PRIVILEGE String => name of access (SELECT, * INSERT, UPDATE, REFRENCES, ...) *
  7. IS_GRANTABLE String => ""YES"" if grantee is permitted * to grant to others; ""NO"" if not; null if unknown *
* * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param tableNamePattern a table name pattern; must match the * table name as it is stored in the database * @return ResultSet - each row is a table privilege description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape */ ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException; /** * Retrieves a description of a table's optimal set of columns that * uniquely identifies a row. They are ordered by SCOPE. * *

Each column description has the following columns: *

    *
  1. SCOPE short => actual scope of result *
      *
    • bestRowTemporary - very temporary, while using row *
    • bestRowTransaction - valid for remainder of current transaction *
    • bestRowSession - valid for remainder of current session *
    *
  2. COLUMN_NAME String => column name *
  3. DATA_TYPE int => SQL data type from java.sql.Types *
  4. TYPE_NAME String => Data source dependent type name, * for a UDT the type name is fully qualified *
  5. COLUMN_SIZE int => precision *
  6. BUFFER_LENGTH int => not used *
  7. DECIMAL_DIGITS short => scale - Null is returned for data types where * DECIMAL_DIGITS is not applicable. *
  8. PSEUDO_COLUMN short => is this a pseudo column * like an Oracle ROWID *
      *
    • bestRowUnknown - may or may not be pseudo column *
    • bestRowNotPseudo - is NOT a pseudo column *
    • bestRowPseudo - is a pseudo column *
    *
* *

The COLUMN_SIZE column represents the specified column size for the given column. * For numeric data, this is the maximum precision. For character data, this is the length in characters. * For datetime datatypes, this is the length in characters of the String representation (assuming the * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype, * this is the length in bytes. Null is returned for data types where the * column size is not applicable. * * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schema a schema name; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param table a table name; must match the table name as it is stored * in the database * @param scope the scope of interest; use same values as SCOPE * @param nullable include columns that are nullable. * @return ResultSet - each row is a column description * @exception SQLException if a database access error occurs */ ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException; /** * Indicates that the scope of the best row identifier is * very temporary, lasting only while the * row is being used. *

* A possible value for the column * SCOPE * in the ResultSet object * returned by the method getBestRowIdentifier. */ int bestRowTemporary = 0; /** * Indicates that the scope of the best row identifier is * the remainder of the current transaction. *

* A possible value for the column * SCOPE * in the ResultSet object * returned by the method getBestRowIdentifier. */ int bestRowTransaction = 1; /** * Indicates that the scope of the best row identifier is * the remainder of the current session. *

* A possible value for the column * SCOPE * in the ResultSet object * returned by the method getBestRowIdentifier. */ int bestRowSession = 2; /** * Indicates that the best row identifier may or may not be a pseudo column. *

* A possible value for the column * PSEUDO_COLUMN * in the ResultSet object * returned by the method getBestRowIdentifier. */ int bestRowUnknown = 0; /** * Indicates that the best row identifier is NOT a pseudo column. *

* A possible value for the column * PSEUDO_COLUMN * in the ResultSet object * returned by the method getBestRowIdentifier. */ int bestRowNotPseudo = 1; /** * Indicates that the best row identifier is a pseudo column. *

* A possible value for the column * PSEUDO_COLUMN * in the ResultSet object * returned by the method getBestRowIdentifier. */ int bestRowPseudo = 2; /** * Retrieves a description of a table's columns that are automatically * updated when any value in a row is updated. They are * unordered. * *

Each column description has the following columns: *

    *
  1. SCOPE short => is not used *
  2. COLUMN_NAME String => column name *
  3. DATA_TYPE int => SQL data type from java.sql.Types *
  4. TYPE_NAME String => Data source-dependent type name *
  5. COLUMN_SIZE int => precision *
  6. BUFFER_LENGTH int => length of column value in bytes *
  7. DECIMAL_DIGITS short => scale - Null is returned for data types where * DECIMAL_DIGITS is not applicable. *
  8. PSEUDO_COLUMN short => whether this is pseudo column * like an Oracle ROWID *
      *
    • versionColumnUnknown - may or may not be pseudo column *
    • versionColumnNotPseudo - is NOT a pseudo column *
    • versionColumnPseudo - is a pseudo column *
    *
* *

The COLUMN_SIZE column represents the specified column size for the given column. * For numeric data, this is the maximum precision. For character data, this is the length in characters. * For datetime datatypes, this is the length in characters of the String representation (assuming the * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype, * this is the length in bytes. Null is returned for data types where the * column size is not applicable. * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schema a schema name; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param table a table name; must match the table name as it is stored * in the database * @return a ResultSet object in which each row is a * column description * @exception SQLException if a database access error occurs */ ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException; /** * Indicates that this version column may or may not be a pseudo column. *

* A possible value for the column * PSEUDO_COLUMN * in the ResultSet object * returned by the method getVersionColumns. */ int versionColumnUnknown = 0; /** * Indicates that this version column is NOT a pseudo column. *

* A possible value for the column * PSEUDO_COLUMN * in the ResultSet object * returned by the method getVersionColumns. */ int versionColumnNotPseudo = 1; /** * Indicates that this version column is a pseudo column. *

* A possible value for the column * PSEUDO_COLUMN * in the ResultSet object * returned by the method getVersionColumns. */ int versionColumnPseudo = 2; /** * Retrieves a description of the given table's primary key columns. They * are ordered by COLUMN_NAME. * *

Each primary key column description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. COLUMN_NAME String => column name *
  5. KEY_SEQ short => sequence number within primary key( a value * of 1 represents the first column of the primary key, a value of 2 would * represent the second column within the primary key). *
  6. PK_NAME String => primary key name (may be null) *
* * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schema a schema name; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param table a table name; must match the table name as it is stored * in the database * @return ResultSet - each row is a primary key column description * @exception SQLException if a database access error occurs */ ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException; /** * Retrieves a description of the primary key columns that are * referenced by the given table's foreign key columns (the primary keys * imported by a table). They are ordered by PKTABLE_CAT, * PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ. * *

Each primary key column description has the following columns: *

    *
  1. PKTABLE_CAT String => primary key table catalog * being imported (may be null) *
  2. PKTABLE_SCHEM String => primary key table schema * being imported (may be null) *
  3. PKTABLE_NAME String => primary key table name * being imported *
  4. PKCOLUMN_NAME String => primary key column name * being imported *
  5. FKTABLE_CAT String => foreign key table catalog (may be null) *
  6. FKTABLE_SCHEM String => foreign key table schema (may be null) *
  7. FKTABLE_NAME String => foreign key table name *
  8. FKCOLUMN_NAME String => foreign key column name *
  9. KEY_SEQ short => sequence number within a foreign key( a value * of 1 represents the first column of the foreign key, a value of 2 would * represent the second column within the foreign key). *
  10. UPDATE_RULE short => What happens to a * foreign key when the primary key is updated: *
      *
    • importedNoAction - do not allow update of primary * key if it has been imported *
    • importedKeyCascade - change imported key to agree * with primary key update *
    • importedKeySetNull - change imported key to NULL * if its primary key has been updated *
    • importedKeySetDefault - change imported key to default values * if its primary key has been updated *
    • importedKeyRestrict - same as importedKeyNoAction * (for ODBC 2.x compatibility) *
    *
  11. DELETE_RULE short => What happens to * the foreign key when primary is deleted. *
      *
    • importedKeyNoAction - do not allow delete of primary * key if it has been imported *
    • importedKeyCascade - delete rows that import a deleted key *
    • importedKeySetNull - change imported key to NULL if * its primary key has been deleted *
    • importedKeyRestrict - same as importedKeyNoAction * (for ODBC 2.x compatibility) *
    • importedKeySetDefault - change imported key to default if * its primary key has been deleted *
    *
  12. FK_NAME String => foreign key name (may be null) *
  13. PK_NAME String => primary key name (may be null) *
  14. DEFERRABILITY short => can the evaluation of foreign key * constraints be deferred until commit *
      *
    • importedKeyInitiallyDeferred - see SQL92 for definition *
    • importedKeyInitiallyImmediate - see SQL92 for definition *
    • importedKeyNotDeferrable - see SQL92 for definition *
    *
* * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schema a schema name; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param table a table name; must match the table name as it is stored * in the database * @return ResultSet - each row is a primary key column description * @exception SQLException if a database access error occurs * @see #getExportedKeys */ ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException; /** * For the column UPDATE_RULE, * indicates that * when the primary key is updated, the foreign key (imported key) * is changed to agree with it. * For the column DELETE_RULE, * it indicates that * when the primary key is deleted, rows that imported that key * are deleted. *

* A possible value for the columns UPDATE_RULE * and DELETE_RULE in the * ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeyCascade = 0; /** * For the column UPDATE_RULE, indicates that * a primary key may not be updated if it has been imported by * another table as a foreign key. * For the column DELETE_RULE, indicates that * a primary key may not be deleted if it has been imported by * another table as a foreign key. *

* A possible value for the columns UPDATE_RULE * and DELETE_RULE in the * ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeyRestrict = 1; /** * For the columns UPDATE_RULE * and DELETE_RULE, indicates that * when the primary key is updated or deleted, the foreign key (imported key) * is changed to NULL. *

* A possible value for the columns UPDATE_RULE * and DELETE_RULE in the * ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeySetNull = 2; /** * For the columns UPDATE_RULE * and DELETE_RULE, indicates that * if the primary key has been imported, it cannot be updated or deleted. *

* A possible value for the columns UPDATE_RULE * and DELETE_RULE in the * ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeyNoAction = 3; /** * For the columns UPDATE_RULE * and DELETE_RULE, indicates that * if the primary key is updated or deleted, the foreign key (imported key) * is set to the default value. *

* A possible value for the columns UPDATE_RULE * and DELETE_RULE in the * ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeySetDefault = 4; /** * Indicates deferrability. See SQL-92 for a definition. *

* A possible value for the column DEFERRABILITY * in the ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeyInitiallyDeferred = 5; /** * Indicates deferrability. See SQL-92 for a definition. *

* A possible value for the column DEFERRABILITY * in the ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeyInitiallyImmediate = 6; /** * Indicates deferrability. See SQL-92 for a definition. *

* A possible value for the column DEFERRABILITY * in the ResultSet objects returned by the methods * getImportedKeys, getExportedKeys, * and getCrossReference. */ int importedKeyNotDeferrable = 7; /** * Retrieves a description of the foreign key columns that reference the * given table's primary key columns (the foreign keys exported by a * table). They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, * FKTABLE_NAME, and KEY_SEQ. * *

Each foreign key column description has the following columns: *

    *
  1. PKTABLE_CAT String => primary key table catalog (may be null) *
  2. PKTABLE_SCHEM String => primary key table schema (may be null) *
  3. PKTABLE_NAME String => primary key table name *
  4. PKCOLUMN_NAME String => primary key column name *
  5. FKTABLE_CAT String => foreign key table catalog (may be null) * being exported (may be null) *
  6. FKTABLE_SCHEM String => foreign key table schema (may be null) * being exported (may be null) *
  7. FKTABLE_NAME String => foreign key table name * being exported *
  8. FKCOLUMN_NAME String => foreign key column name * being exported *
  9. KEY_SEQ short => sequence number within foreign key( a value * of 1 represents the first column of the foreign key, a value of 2 would * represent the second column within the foreign key). *
  10. UPDATE_RULE short => What happens to * foreign key when primary is updated: *
      *
    • importedNoAction - do not allow update of primary * key if it has been imported *
    • importedKeyCascade - change imported key to agree * with primary key update *
    • importedKeySetNull - change imported key to NULL if * its primary key has been updated *
    • importedKeySetDefault - change imported key to default values * if its primary key has been updated *
    • importedKeyRestrict - same as importedKeyNoAction * (for ODBC 2.x compatibility) *
    *
  11. DELETE_RULE short => What happens to * the foreign key when primary is deleted. *
      *
    • importedKeyNoAction - do not allow delete of primary * key if it has been imported *
    • importedKeyCascade - delete rows that import a deleted key *
    • importedKeySetNull - change imported key to NULL if * its primary key has been deleted *
    • importedKeyRestrict - same as importedKeyNoAction * (for ODBC 2.x compatibility) *
    • importedKeySetDefault - change imported key to default if * its primary key has been deleted *
    *
  12. FK_NAME String => foreign key name (may be null) *
  13. PK_NAME String => primary key name (may be null) *
  14. DEFERRABILITY short => can the evaluation of foreign key * constraints be deferred until commit *
      *
    • importedKeyInitiallyDeferred - see SQL92 for definition *
    • importedKeyInitiallyImmediate - see SQL92 for definition *
    • importedKeyNotDeferrable - see SQL92 for definition *
    *
* * @param catalog a catalog name; must match the catalog name as it * is stored in this database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schema a schema name; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param table a table name; must match the table name as it is stored * in this database * @return a ResultSet object in which each row is a * foreign key column description * @exception SQLException if a database access error occurs * @see #getImportedKeys */ ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException; /** * Retrieves a description of the foreign key columns in the given foreign key * table that reference the primary key or the columns representing a unique constraint of the parent table (could be the same or a different table). * The number of columns returned from the parent table must match the number of * columns that make up the foreign key. They * are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and * KEY_SEQ. * *

Each foreign key column description has the following columns: *

    *
  1. PKTABLE_CAT String => parent key table catalog (may be null) *
  2. PKTABLE_SCHEM String => parent key table schema (may be null) *
  3. PKTABLE_NAME String => parent key table name *
  4. PKCOLUMN_NAME String => parent key column name *
  5. FKTABLE_CAT String => foreign key table catalog (may be null) * being exported (may be null) *
  6. FKTABLE_SCHEM String => foreign key table schema (may be null) * being exported (may be null) *
  7. FKTABLE_NAME String => foreign key table name * being exported *
  8. FKCOLUMN_NAME String => foreign key column name * being exported *
  9. KEY_SEQ short => sequence number within foreign key( a value * of 1 represents the first column of the foreign key, a value of 2 would * represent the second column within the foreign key). *
  10. UPDATE_RULE short => What happens to * foreign key when parent key is updated: *
      *
    • importedNoAction - do not allow update of parent * key if it has been imported *
    • importedKeyCascade - change imported key to agree * with parent key update *
    • importedKeySetNull - change imported key to NULL if * its parent key has been updated *
    • importedKeySetDefault - change imported key to default values * if its parent key has been updated *
    • importedKeyRestrict - same as importedKeyNoAction * (for ODBC 2.x compatibility) *
    *
  11. DELETE_RULE short => What happens to * the foreign key when parent key is deleted. *
      *
    • importedKeyNoAction - do not allow delete of parent * key if it has been imported *
    • importedKeyCascade - delete rows that import a deleted key *
    • importedKeySetNull - change imported key to NULL if * its primary key has been deleted *
    • importedKeyRestrict - same as importedKeyNoAction * (for ODBC 2.x compatibility) *
    • importedKeySetDefault - change imported key to default if * its parent key has been deleted *
    *
  12. FK_NAME String => foreign key name (may be null) *
  13. PK_NAME String => parent key name (may be null) *
  14. DEFERRABILITY short => can the evaluation of foreign key * constraints be deferred until commit *
      *
    • importedKeyInitiallyDeferred - see SQL92 for definition *
    • importedKeyInitiallyImmediate - see SQL92 for definition *
    • importedKeyNotDeferrable - see SQL92 for definition *
    *
* * @param parentCatalog a catalog name; must match the catalog name * as it is stored in the database; """" retrieves those without a * catalog; null means drop catalog name from the selection criteria * @param parentSchema a schema name; must match the schema name as * it is stored in the database; """" retrieves those without a schema; * null means drop schema name from the selection criteria * @param parentTable the name of the table that exports the key; must match * the table name as it is stored in the database * @param foreignCatalog a catalog name; must match the catalog name as * it is stored in the database; """" retrieves those without a * catalog; null means drop catalog name from the selection criteria * @param foreignSchema a schema name; must match the schema name as it * is stored in the database; """" retrieves those without a schema; * null means drop schema name from the selection criteria * @param foreignTable the name of the table that imports the key; must match * the table name as it is stored in the database * @return ResultSet - each row is a foreign key column description * @exception SQLException if a database access error occurs * @see #getImportedKeys */ ResultSet getCrossReference( String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable ) throws SQLException; /** * Retrieves a description of all the data types supported by * this database. They are ordered by DATA_TYPE and then by how * closely the data type maps to the corresponding JDBC SQL type. * *

If the database supports SQL distinct types, then getTypeInfo() will return * a single row with a TYPE_NAME of DISTINCT and a DATA_TYPE of Types.DISTINCT. * If the database supports SQL structured types, then getTypeInfo() will return * a single row with a TYPE_NAME of STRUCT and a DATA_TYPE of Types.STRUCT. * *

If SQL distinct or structured types are supported, then information on the * individual types may be obtained from the getUDTs() method. * * *

Each type description has the following columns: *

    *
  1. TYPE_NAME String => Type name *
  2. DATA_TYPE int => SQL data type from java.sql.Types *
  3. PRECISION int => maximum precision *
  4. LITERAL_PREFIX String => prefix used to quote a literal * (may be null) *
  5. LITERAL_SUFFIX String => suffix used to quote a literal (may be null) *
  6. CREATE_PARAMS String => parameters used in creating * the type (may be null) *
  7. NULLABLE short => can you use NULL for this type. *
      *
    • typeNoNulls - does not allow NULL values *
    • typeNullable - allows NULL values *
    • typeNullableUnknown - nullability unknown *
    *
  8. CASE_SENSITIVE boolean=> is it case sensitive. *
  9. SEARCHABLE short => can you use ""WHERE"" based on this type: *
      *
    • typePredNone - No support *
    • typePredChar - Only supported with WHERE .. LIKE *
    • typePredBasic - Supported except for WHERE .. LIKE *
    • typeSearchable - Supported for all WHERE .. *
    *
  10. UNSIGNED_ATTRIBUTE boolean => is it unsigned. *
  11. FIXED_PREC_SCALE boolean => can it be a money value. *
  12. AUTO_INCREMENT boolean => can it be used for an * auto-increment value. *
  13. LOCAL_TYPE_NAME String => localized version of type name * (may be null) *
  14. MINIMUM_SCALE short => minimum scale supported *
  15. MAXIMUM_SCALE short => maximum scale supported *
  16. SQL_DATA_TYPE int => unused *
  17. SQL_DATETIME_SUB int => unused *
  18. NUM_PREC_RADIX int => usually 2 or 10 *
* *

The PRECISION column represents the maximum column size that the server supports for the given datatype. * For numeric data, this is the maximum precision. For character data, this is the length in characters. * For datetime datatypes, this is the length in characters of the String representation (assuming the * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype, * this is the length in bytes. Null is returned for data types where the * column size is not applicable. * * @return a ResultSet object in which each row is an SQL * type description * @exception SQLException if a database access error occurs */ ResultSet getTypeInfo() throws SQLException; /** * Indicates that a NULL value is NOT allowed for this * data type. *

* A possible value for column NULLABLE in the * ResultSet object returned by the method * getTypeInfo. */ int typeNoNulls = 0; /** * Indicates that a NULL value is allowed for this * data type. *

* A possible value for column NULLABLE in the * ResultSet object returned by the method * getTypeInfo. */ int typeNullable = 1; /** * Indicates that it is not known whether a NULL value * is allowed for this data type. *

* A possible value for column NULLABLE in the * ResultSet object returned by the method * getTypeInfo. */ int typeNullableUnknown = 2; /** * Indicates that WHERE search clauses are not supported * for this type. *

* A possible value for column SEARCHABLE in the * ResultSet object returned by the method * getTypeInfo. */ int typePredNone = 0; /** * Indicates that the data type * can be only be used in WHERE search clauses * that use LIKE predicates. *

* A possible value for column SEARCHABLE in the * ResultSet object returned by the method * getTypeInfo. */ int typePredChar = 1; /** * Indicates that the data type can be only be used in WHERE * search clauses * that do not use LIKE predicates. *

* A possible value for column SEARCHABLE in the * ResultSet object returned by the method * getTypeInfo. */ int typePredBasic = 2; /** * Indicates that all WHERE search clauses can be * based on this type. *

* A possible value for column SEARCHABLE in the * ResultSet object returned by the method * getTypeInfo. */ int typeSearchable = 3; /** * Retrieves a description of the given table's indices and statistics. They are * ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION. * *

Each index column description has the following columns: *

    *
  1. TABLE_CAT String => table catalog (may be null) *
  2. TABLE_SCHEM String => table schema (may be null) *
  3. TABLE_NAME String => table name *
  4. NON_UNIQUE boolean => Can index values be non-unique. * false when TYPE is tableIndexStatistic *
  5. INDEX_QUALIFIER String => index catalog (may be null); * null when TYPE is tableIndexStatistic *
  6. INDEX_NAME String => index name; null when TYPE is * tableIndexStatistic *
  7. TYPE short => index type: *
      *
    • tableIndexStatistic - this identifies table statistics that are * returned in conjuction with a table's index descriptions *
    • tableIndexClustered - this is a clustered index *
    • tableIndexHashed - this is a hashed index *
    • tableIndexOther - this is some other style of index *
    *
  8. ORDINAL_POSITION short => column sequence number * within index; zero when TYPE is tableIndexStatistic *
  9. COLUMN_NAME String => column name; null when TYPE is * tableIndexStatistic *
  10. ASC_OR_DESC String => column sort sequence, ""A"" => ascending, * ""D"" => descending, may be null if sort sequence is not supported; * null when TYPE is tableIndexStatistic *
  11. CARDINALITY int => When TYPE is tableIndexStatistic, then * this is the number of rows in the table; otherwise, it is the * number of unique values in the index. *
  12. PAGES int => When TYPE is tableIndexStatisic then * this is the number of pages used for the table, otherwise it * is the number of pages used for the current index. *
  13. FILTER_CONDITION String => Filter condition, if any. * (may be null) *
* * @param catalog a catalog name; must match the catalog name as it * is stored in this database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schema a schema name; must match the schema name * as it is stored in this database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param table a table name; must match the table name as it is stored * in this database * @param unique when true, return only indices for unique values; * when false, return indices regardless of whether unique or not * @param approximate when true, result is allowed to reflect approximate * or out of data values; when false, results are requested to be * accurate * @return ResultSet - each row is an index column description * @exception SQLException if a database access error occurs */ ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException; /** * Indicates that this column contains table statistics that * are returned in conjunction with a table's index descriptions. *

* A possible value for column TYPE in the * ResultSet object returned by the method * getIndexInfo. */ short tableIndexStatistic = 0; /** * Indicates that this table index is a clustered index. *

* A possible value for column TYPE in the * ResultSet object returned by the method * getIndexInfo. */ short tableIndexClustered = 1; /** * Indicates that this table index is a hashed index. *

* A possible value for column TYPE in the * ResultSet object returned by the method * getIndexInfo. */ short tableIndexHashed = 2; /** * Indicates that this table index is not a clustered * index, a hashed index, or table statistics; * it is something other than these. *

* A possible value for column TYPE in the * ResultSet object returned by the method * getIndexInfo. */ short tableIndexOther = 3; //--------------------------JDBC 2.0----------------------------- /** * Retrieves whether this database supports the given result set type. * * @param type defined in java.sql.ResultSet * @return true if so; false otherwise * @exception SQLException if a database access error occurs * @see Connection * @since 1.2 */ boolean supportsResultSetType(int type) throws SQLException; /** * Retrieves whether this database supports the given concurrency type * in combination with the given result set type. * * @param type defined in java.sql.ResultSet * @param concurrency type defined in java.sql.ResultSet * @return true if so; false otherwise * @exception SQLException if a database access error occurs * @see Connection * @since 1.2 */ boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException; /** * * Retrieves whether for the given type of ResultSet object, * the result set's own updates are visible. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if updates are visible for the given result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean ownUpdatesAreVisible(int type) throws SQLException; /** * Retrieves whether a result set's own deletes are visible. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if deletes are visible for the given result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean ownDeletesAreVisible(int type) throws SQLException; /** * Retrieves whether a result set's own inserts are visible. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if inserts are visible for the given result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean ownInsertsAreVisible(int type) throws SQLException; /** * Retrieves whether updates made by others are visible. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if updates made by others * are visible for the given result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean othersUpdatesAreVisible(int type) throws SQLException; /** * Retrieves whether deletes made by others are visible. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if deletes made by others * are visible for the given result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean othersDeletesAreVisible(int type) throws SQLException; /** * Retrieves whether inserts made by others are visible. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if inserts made by others * are visible for the given result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean othersInsertsAreVisible(int type) throws SQLException; /** * Retrieves whether or not a visible row update can be detected by * calling the method ResultSet.rowUpdated. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if changes are detected by the result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean updatesAreDetected(int type) throws SQLException; /** * Retrieves whether or not a visible row delete can be detected by * calling the method ResultSet.rowDeleted. If the method * deletesAreDetected returns false, it means that * deleted rows are removed from the result set. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if deletes are detected by the given result set type; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean deletesAreDetected(int type) throws SQLException; /** * Retrieves whether or not a visible row insert can be detected * by calling the method ResultSet.rowInserted. * * @param type the ResultSet type; one of * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @return true if changes are detected by the specified result * set type; false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean insertsAreDetected(int type) throws SQLException; /** * Retrieves whether this database supports batch updates. * * @return true if this database supports batch upcates; * false otherwise * @exception SQLException if a database access error occurs * @since 1.2 */ boolean supportsBatchUpdates() throws SQLException; /** * Retrieves a description of the user-defined types (UDTs) defined * in a particular schema. Schema-specific UDTs may have type * JAVA_OBJECT, STRUCT, * or DISTINCT. * *

Only types matching the catalog, schema, type name and type * criteria are returned. They are ordered by DATA_TYPE, * TYPE_CAT, TYPE_SCHEM and * TYPE_NAME. The type name parameter may be a fully-qualified * name. In this case, the catalog and schemaPattern parameters are * ignored. * *

Each type description has the following columns: *

    *
  1. TYPE_CAT String => the type's catalog (may be null) *
  2. TYPE_SCHEM String => type's schema (may be null) *
  3. TYPE_NAME String => type name *
  4. CLASS_NAME String => Java class name *
  5. DATA_TYPE int => type value defined in java.sql.Types. * One of JAVA_OBJECT, STRUCT, or DISTINCT *
  6. REMARKS String => explanatory comment on the type *
  7. BASE_TYPE short => type code of the source type of a * DISTINCT type or the type that implements the user-generated * reference type of the SELF_REFERENCING_COLUMN of a structured * type as defined in java.sql.Types (null if DATA_TYPE is not * DISTINCT or not STRUCT with REFERENCE_GENERATION = USER_DEFINED) *
* *

Note: If the driver does not support UDTs, an empty * result set is returned. * * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema pattern name; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param typeNamePattern a type name pattern; must match the type name * as it is stored in the database; may be a fully qualified name * @param types a list of user-defined types (JAVA_OBJECT, * STRUCT, or DISTINCT) to include; null returns all types * @return ResultSet object in which each row describes a UDT * @exception SQLException if a database access error occurs * @see #getSearchStringEscape * @since 1.2 */ ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException; /** * Retrieves the connection that produced this metadata object. *

* @return the connection that produced this metadata object * @exception SQLException if a database access error occurs * @since 1.2 */ Connection getConnection() throws SQLException; // ------------------- JDBC 3.0 ------------------------- /** * Retrieves whether this database supports savepoints. * * @return true if savepoints are supported; * false otherwise * @exception SQLException if a database access error occurs * @since 1.4 */ boolean supportsSavepoints() throws SQLException; /** * Retrieves whether this database supports named parameters to callable * statements. * * @return true if named parameters are supported; * false otherwise * @exception SQLException if a database access error occurs * @since 1.4 */ boolean supportsNamedParameters() throws SQLException; /** * Retrieves whether it is possible to have multiple ResultSet objects * returned from a CallableStatement object * simultaneously. * * @return true if a CallableStatement object * can return multiple ResultSet objects * simultaneously; false otherwise * @exception SQLException if a datanase access error occurs * @since 1.4 */ boolean supportsMultipleOpenResults() throws SQLException; /** * Retrieves whether auto-generated keys can be retrieved after * a statement has been executed * * @return true if auto-generated keys can be retrieved * after a statement has executed; false otherwise *

If true is returned, the JDBC driver must support the * returning of auto-generated keys for at least SQL INSERT statements *

* @exception SQLException if a database access error occurs * @since 1.4 */ boolean supportsGetGeneratedKeys() throws SQLException; /** * Retrieves a description of the user-defined type (UDT) hierarchies defined in a * particular schema in this database. Only the immediate super type/ * sub type relationship is modeled. *

* Only supertype information for UDTs matching the catalog, * schema, and type name is returned. The type name parameter * may be a fully-qualified name. When the UDT name supplied is a * fully-qualified name, the catalog and schemaPattern parameters are * ignored. *

* If a UDT does not have a direct super type, it is not listed here. * A row of the ResultSet object returned by this method * describes the designated UDT and a direct supertype. A row has the following * columns: *

    *
  1. TYPE_CAT String => the UDT's catalog (may be null) *
  2. TYPE_SCHEM String => UDT's schema (may be null) *
  3. TYPE_NAME String => type name of the UDT *
  4. SUPERTYPE_CAT String => the direct super type's catalog * (may be null) *
  5. SUPERTYPE_SCHEM String => the direct super type's schema * (may be null) *
  6. SUPERTYPE_NAME String => the direct super type's name *
* *

Note: If the driver does not support type hierarchies, an * empty result set is returned. * * @param catalog a catalog name; """" retrieves those without a catalog; * null means drop catalog name from the selection criteria * @param schemaPattern a schema name pattern; """" retrieves those * without a schema * @param typeNamePattern a UDT name pattern; may be a fully-qualified * name * @return a ResultSet object in which a row gives information * about the designated UDT * @throws SQLException if a database access error occurs * @see #getSearchStringEscape * @since 1.4 */ ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException; /** * Retrieves a description of the table hierarchies defined in a particular * schema in this database. * *

Only supertable information for tables matching the catalog, schema * and table name are returned. The table name parameter may be a fully- * qualified name, in which case, the catalog and schemaPattern parameters * are ignored. If a table does not have a super table, it is not listed here. * Supertables have to be defined in the same catalog and schema as the * sub tables. Therefore, the type description does not need to include * this information for the supertable. * *

Each type description has the following columns: *

    *
  1. TABLE_CAT String => the type's catalog (may be null) *
  2. TABLE_SCHEM String => type's schema (may be null) *
  3. TABLE_NAME String => type name *
  4. SUPERTABLE_NAME String => the direct super type's name *
* *

Note: If the driver does not support type hierarchies, an * empty result set is returned. * * @param catalog a catalog name; """" retrieves those without a catalog; * null means drop catalog name from the selection criteria * @param schemaPattern a schema name pattern; """" retrieves those * without a schema * @param tableNamePattern a table name pattern; may be a fully-qualified * name * @return a ResultSet object in which each row is a type description * @throws SQLException if a database access error occurs * @see #getSearchStringEscape * @since 1.4 */ ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException; /** * Indicates that NULL values might not be allowed. *

* A possible value for the column * NULLABLE in the ResultSet object * returned by the method getAttributes. */ short attributeNoNulls = 0; /** * Indicates that NULL values are definitely allowed. *

* A possible value for the column NULLABLE * in the ResultSet object * returned by the method getAttributes. */ short attributeNullable = 1; /** * Indicates that whether NULL values are allowed is not * known. *

* A possible value for the column NULLABLE * in the ResultSet object * returned by the method getAttributes. */ short attributeNullableUnknown = 2; /** * Retrieves a description of the given attribute of the given type * for a user-defined type (UDT) that is available in the given schema * and catalog. *

* Descriptions are returned only for attributes of UDTs matching the * catalog, schema, type, and attribute name criteria. They are ordered by * TYPE_CAT, TYPE_SCHEM, * TYPE_NAME and ORDINAL_POSITION. This description * does not contain inherited attributes. *

* The ResultSet object that is returned has the following * columns: *

    *
  1. TYPE_CAT String => type catalog (may be null) *
  2. TYPE_SCHEM String => type schema (may be null) *
  3. TYPE_NAME String => type name *
  4. ATTR_NAME String => attribute name *
  5. DATA_TYPE int => attribute type SQL type from java.sql.Types *
  6. ATTR_TYPE_NAME String => Data source dependent type name. * For a UDT, the type name is fully qualified. For a REF, the type name is * fully qualified and represents the target type of the reference type. *
  7. ATTR_SIZE int => column size. For char or date * types this is the maximum number of characters; for numeric or * decimal types this is precision. *
  8. DECIMAL_DIGITS int => the number of fractional digits. Null is returned for data types where * DECIMAL_DIGITS is not applicable. *
  9. NUM_PREC_RADIX int => Radix (typically either 10 or 2) *
  10. NULLABLE int => whether NULL is allowed *
      *
    • attributeNoNulls - might not allow NULL values *
    • attributeNullable - definitely allows NULL values *
    • attributeNullableUnknown - nullability unknown *
    *
  11. REMARKS String => comment describing column (may be null) *
  12. ATTR_DEF String => default value (may be null) *
  13. SQL_DATA_TYPE int => unused *
  14. SQL_DATETIME_SUB int => unused *
  15. CHAR_OCTET_LENGTH int => for char types the * maximum number of bytes in the column *
  16. ORDINAL_POSITION int => index of the attribute in the UDT * (starting at 1) *
  17. IS_NULLABLE String => ISO rules are used to determine * the nullability for a attribute. *
      *
    • YES --- if the attribute can include NULLs *
    • NO --- if the attribute cannot include NULLs *
    • empty string --- if the nullability for the * attribute is unknown *
    *
  18. SCOPE_CATALOG String => catalog of table that is the * scope of a reference attribute (null if DATA_TYPE isn't REF) *
  19. SCOPE_SCHEMA String => schema of table that is the * scope of a reference attribute (null if DATA_TYPE isn't REF) *
  20. SCOPE_TABLE String => table name that is the scope of a * reference attribute (null if the DATA_TYPE isn't REF) *
  21. SOURCE_DATA_TYPE short => source type of a distinct type or user-generated * Ref type,SQL type from java.sql.Types (null if DATA_TYPE * isn't DISTINCT or user-generated REF) *
* @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param typeNamePattern a type name pattern; must match the * type name as it is stored in the database * @param attributeNamePattern an attribute name pattern; must match the attribute * name as it is declared in the database * @return a ResultSet object in which each row is an * attribute description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape * @since 1.4 */ ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException; /** * Retrieves whether this database supports the given result set holdability. * * @param holdability one of the following constants: * ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @return true if so; false otherwise * @exception SQLException if a database access error occurs * @see Connection * @since 1.4 */ boolean supportsResultSetHoldability(int holdability) throws SQLException; /** * Retrieves this database's default holdability for ResultSet * objects. * * @return the default holdability; either * ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @exception SQLException if a database access error occurs * @since 1.4 */ int getResultSetHoldability() throws SQLException; /** * Retrieves the major version number of the underlying database. * * @return the underlying database's major version * @exception SQLException if a database access error occurs * @since 1.4 */ int getDatabaseMajorVersion() throws SQLException; /** * Retrieves the minor version number of the underlying database. * * @return underlying database's minor version * @exception SQLException if a database access error occurs * @since 1.4 */ int getDatabaseMinorVersion() throws SQLException; /** * Retrieves the major JDBC version number for this * driver. * * @return JDBC version major number * @exception SQLException if a database access error occurs * @since 1.4 */ int getJDBCMajorVersion() throws SQLException; /** * Retrieves the minor JDBC version number for this * driver. * * @return JDBC version minor number * @exception SQLException if a database access error occurs * @since 1.4 */ int getJDBCMinorVersion() throws SQLException; /** * A possible return value for the method * DatabaseMetaData.getSQLStateType which is used to indicate * whether the value returned by the method * SQLException.getSQLState is an * X/Open (now know as Open Group) SQL CLI SQLSTATE value. *

* @since 1.4 */ int sqlStateXOpen = 1; /** * A possible return value for the method * DatabaseMetaData.getSQLStateType which is used to indicate * whether the value returned by the method * SQLException.getSQLState is an SQLSTATE value. *

* @since 1.6 */ int sqlStateSQL = 2; /** * A possible return value for the method * DatabaseMetaData.getSQLStateType which is used to indicate * whether the value returned by the method * SQLException.getSQLState is an SQL99 SQLSTATE value. *

* Note:This constant remains only for compatibility reasons. Developers * should use the constant sqlStateSQL instead. * * @since 1.4 */ int sqlStateSQL99 = sqlStateSQL; /** * Indicates whether the SQLSTATE returned by SQLException.getSQLState * is X/Open (now known as Open Group) SQL CLI or SQL:2003. * @return the type of SQLSTATE; one of: * sqlStateXOpen or * sqlStateSQL * @throws SQLException if a database access error occurs * @since 1.4 */ int getSQLStateType() throws SQLException; /** * Indicates whether updates made to a LOB are made on a copy or directly * to the LOB. * @return true if updates are made to a copy of the LOB; * false if updates are made directly to the LOB * @throws SQLException if a database access error occurs * @since 1.4 */ boolean locatorsUpdateCopy() throws SQLException; /** * Retrieves whether this database supports statement pooling. * * @return true if so; false otherwise * @throws SQLException if a database access error occurs * @since 1.4 */ boolean supportsStatementPooling() throws SQLException; //------------------------- JDBC 4.0 ----------------------------------- /** * Indicates whether or not this data source supports the SQL ROWID type, * and if so the lifetime for which a RowId object remains valid. *

* The returned int values have the following relationship: *

     *     ROWID_UNSUPPORTED < ROWID_VALID_OTHER < ROWID_VALID_TRANSACTION
     *         < ROWID_VALID_SESSION < ROWID_VALID_FOREVER
     * 
* so conditional logic such as *
     *     if (metadata.getRowIdLifetime() > DatabaseMetaData.ROWID_VALID_TRANSACTION)
     * 
* can be used. Valid Forever means valid across all Sessions, and valid for * a Session means valid across all its contained Transactions. * * @return the status indicating the lifetime of a RowId * @throws SQLException if a database access error occurs * @since 1.6 */ RowIdLifetime getRowIdLifetime() throws SQLException; /** * Retrieves the schema names available in this database. The results * are ordered by TABLE_CATALOG and * TABLE_SCHEM. * *

The schema columns are: *

    *
  1. TABLE_SCHEM String => schema name *
  2. TABLE_CATALOG String => catalog name (may be null) *
* * * @param catalog a catalog name; must match the catalog name as it is stored * in the database;"""" retrieves those without a catalog; null means catalog * name should not be used to narrow down the search. * @param schemaPattern a schema name; must match the schema name as it is * stored in the database; null means * schema name should not be used to narrow down the search. * @return a ResultSet object in which each row is a * schema description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape * @since 1.6 */ ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException; /** * Retrieves whether this database supports invoking user-defined or vendor functions * using the stored procedure escape syntax. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs * @since 1.6 */ boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException; /** * Retrieves whether a SQLException while autoCommit is true inidcates * that all open ResultSets are closed, even ones that are holdable. When a SQLException occurs while * autocommit is true, it is vendor specific whether the JDBC driver responds with a commit operation, a * rollback operation, or by doing neither a commit nor a rollback. A potential result of this difference * is in whether or not holdable ResultSets are closed. * * @return true if so; false otherwise * @exception SQLException if a database access error occurs * @since 1.6 */ boolean autoCommitFailureClosesAllResultSets() throws SQLException; /** * Retrieves a list of the client info properties * that the driver supports. The result set contains the following columns *

*

    *
  1. NAME String=> The name of the client info property
    *
  2. MAX_LEN int=> The maximum length of the value for the property
    *
  3. DEFAULT_VALUE String=> The default value of the property
    *
  4. DESCRIPTION String=> A description of the property. This will typically * contain information as to where this property is * stored in the database. *
*

* The ResultSet is sorted by the NAME column *

* @return A ResultSet object; each row is a supported client info * property *

* @exception SQLException if a database access error occurs *

* @since 1.6 */ ResultSet getClientInfoProperties() throws SQLException; /** * Retrieves a description of the system and user functions available * in the given catalog. *

* Only system and user function descriptions matching the schema and * function name criteria are returned. They are ordered by * FUNCTION_CAT, FUNCTION_SCHEM, * FUNCTION_NAME and * SPECIFIC_ NAME. * *

Each function description has the the following columns: *

    *
  1. FUNCTION_CAT String => function catalog (may be null) *
  2. FUNCTION_SCHEM String => function schema (may be null) *
  3. FUNCTION_NAME String => function name. This is the name * used to invoke the function *
  4. REMARKS String => explanatory comment on the function *
  5. FUNCTION_TYPE short => kind of function: *
      *
    • functionResultUnknown - Cannot determine if a return value * or table will be returned *
    • functionNoTable- Does not return a table *
    • functionReturnsTable - Returns a table *
    *
  6. SPECIFIC_NAME String => the name which uniquely identifies * this function within its schema. This is a user specified, or DBMS * generated, name that may be different then the FUNCTION_NAME * for example with overload functions *
*

* A user may not have permission to execute any of the functions that are * returned by getFunctions * * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param functionNamePattern a function name pattern; must match the * function name as it is stored in the database * @return ResultSet - each row is a function description * @exception SQLException if a database access error occurs * @see #getSearchStringEscape * @since 1.6 */ ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException; /** * Retrieves a description of the given catalog's system or user * function parameters and return type. * *

Only descriptions matching the schema, function and * parameter name criteria are returned. They are ordered by * FUNCTION_CAT, FUNCTION_SCHEM, * FUNCTION_NAME and * SPECIFIC_ NAME. Within this, the return value, * if any, is first. Next are the parameter descriptions in call * order. The column descriptions follow in column number order. * *

Each row in the ResultSet * is a parameter description, column description or * return type description with the following fields: *

    *
  1. FUNCTION_CAT String => function catalog (may be null) *
  2. FUNCTION_SCHEM String => function schema (may be null) *
  3. FUNCTION_NAME String => function name. This is the name * used to invoke the function *
  4. COLUMN_NAME String => column/parameter name *
  5. COLUMN_TYPE Short => kind of column/parameter: *
      *
    • functionColumnUnknown - nobody knows *
    • functionColumnIn - IN parameter *
    • functionColumnInOut - INOUT parameter *
    • functionColumnOut - OUT parameter *
    • functionColumnReturn - function return value *
    • functionColumnResult - Indicates that the parameter or column * is a column in the ResultSet *
    *
  6. DATA_TYPE int => SQL type from java.sql.Types *
  7. TYPE_NAME String => SQL type name, for a UDT type the * type name is fully qualified *
  8. PRECISION int => precision *
  9. LENGTH int => length in bytes of data *
  10. SCALE short => scale - null is returned for data types where * SCALE is not applicable. *
  11. RADIX short => radix *
  12. NULLABLE short => can it contain NULL. *
      *
    • functionNoNulls - does not allow NULL values *
    • functionNullable - allows NULL values *
    • functionNullableUnknown - nullability unknown *
    *
  13. REMARKS String => comment describing column/parameter *
  14. CHAR_OCTET_LENGTH int => the maximum length of binary * and character based parameters or columns. For any other datatype the returned value * is a NULL *
  15. ORDINAL_POSITION int => the ordinal position, starting * from 1, for the input and output parameters. A value of 0 * is returned if this row describes the function's return value. * For result set columns, it is the * ordinal position of the column in the result set starting from 1. *
  16. IS_NULLABLE String => ISO rules are used to determine * the nullability for a parameter or column. *
      *
    • YES --- if the parameter or column can include NULLs *
    • NO --- if the parameter or column cannot include NULLs *
    • empty string --- if the nullability for the * parameter or column is unknown *
    *
  17. SPECIFIC_NAME String => the name which uniquely identifies * this function within its schema. This is a user specified, or DBMS * generated, name that may be different then the FUNCTION_NAME * for example with overload functions *
* *

The PRECISION column represents the specified column size for the given * parameter or column. * For numeric data, this is the maximum precision. For character data, this is the length in characters. * For datetime datatypes, this is the length in characters of the String representation (assuming the * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype, * this is the length in bytes. Null is returned for data types where the * column size is not applicable. * @param catalog a catalog name; must match the catalog name as it * is stored in the database; """" retrieves those without a catalog; * null means that the catalog name should not be used to narrow * the search * @param schemaPattern a schema name pattern; must match the schema name * as it is stored in the database; """" retrieves those without a schema; * null means that the schema name should not be used to narrow * the search * @param functionNamePattern a procedure name pattern; must match the * function name as it is stored in the database * @param columnNamePattern a parameter name pattern; must match the * parameter or column name as it is stored in the database * @return ResultSet - each row describes a * user function parameter, column or return type * * @exception SQLException if a database access error occurs * @see #getSearchStringEscape * @since 1.6 */ ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException; /** * Indicates that type of the parameter or column is unknown. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getFunctionColumns. */ int functionColumnUnknown = 0; /** * Indicates that the parameter or column is an IN parameter. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getFunctionColumns. * @since 1.6 */ int functionColumnIn = 1; /** * Indicates that the parameter or column is an INOUT parameter. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getFunctionColumns. * @since 1.6 */ int functionColumnInOut = 2; /** * Indicates that the parameter or column is an OUT parameter. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getFunctionColumns. * @since 1.6 */ int functionColumnOut = 3; /** * Indicates that the parameter or column is a return value. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getFunctionColumns. * @since 1.6 */ int functionReturn = 4; /** * Indicates that the parameter or column is a column in a result set. *

* A possible value for the column * COLUMN_TYPE * in the ResultSet * returned by the method getFunctionColumns. * @since 1.6 */ int functionColumnResult = 5; /** * Indicates that NULL values are not allowed. *

* A possible value for the column * NULLABLE * in the ResultSet object * returned by the method getFunctionColumns. * @since 1.6 */ int functionNoNulls = 0; /** * Indicates that NULL values are allowed. *

* A possible value for the column * NULLABLE * in the ResultSet object * returned by the method getFunctionColumns. * @since 1.6 */ int functionNullable = 1; /** * Indicates that whether NULL values are allowed * is unknown. *

* A possible value for the column * NULLABLE * in the ResultSet object * returned by the method getFunctionColumns. * @since 1.6 */ int functionNullableUnknown = 2; /** * Indicates that it is not known whether the function returns * a result or a table. *

* A possible value for column FUNCTION_TYPE in the * ResultSet object returned by the method * getFunctions. * @since 1.6 */ int functionResultUnknown = 0; /** * Indicates that the function does not return a table. *

* A possible value for column FUNCTION_TYPE in the * ResultSet object returned by the method * getFunctions. * @since 1.6 */ int functionNoTable = 1; /** * Indicates that the function returns a table. *

* A possible value for column FUNCTION_TYPE in the * ResultSet object returned by the method * getFunctions. * @since 1.6 */ int functionReturnsTable = 2; // Android-removed: JDBC 4.1 methods were removed immediately after the initial import. } ","procedureNamePattern " "// Copyright 2018 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.buildtool; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.Subscribe; import com.google.common.flogger.GoogleLogger; import com.google.devtools.build.lib.actions.BuildFailedException; import com.google.devtools.build.lib.actions.TestExecException; import com.google.devtools.build.lib.analysis.AnalysisAndExecutionResult; import com.google.devtools.build.lib.analysis.BuildView; import com.google.devtools.build.lib.analysis.BuildView.BuildConfigurationsCreated; import com.google.devtools.build.lib.analysis.BuildView.ExecutionSetup; import com.google.devtools.build.lib.analysis.ViewCreationFailedException; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.CoreOptions; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.test.CoverageArtifactsKnownEvent; import com.google.devtools.build.lib.buildtool.buildevent.NoAnalyzeEvent; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.RepositoryMapping; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.cmdline.TargetParsingException; import com.google.devtools.build.lib.cmdline.TargetPattern; import com.google.devtools.build.lib.cmdline.TargetPattern.Parser; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.pkgcache.LoadingFailedException; import com.google.devtools.build.lib.profiler.ProfilePhase; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.profiler.SilentCloseable; import com.google.devtools.build.lib.runtime.BlazeModule; import com.google.devtools.build.lib.runtime.CommandEnvironment; import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code; import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; import com.google.devtools.build.lib.skyframe.BuildInfoCollectionFunction; import com.google.devtools.build.lib.skyframe.BuildResultListener; import com.google.devtools.build.lib.skyframe.PrecomputedValue; import com.google.devtools.build.lib.skyframe.RepositoryMappingValue.RepositoryMappingResolutionException; import com.google.devtools.build.lib.skyframe.SkyframeBuildView.BuildDriverKeyTestContext; import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue; import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.AspectAnalyzedEvent; import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TestAnalyzedEvent; import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetAnalyzedEvent; import com.google.devtools.build.lib.util.AbruptExitException; import com.google.devtools.build.lib.util.DetailedExitCode; import com.google.devtools.build.lib.util.RegexFilter; import com.google.devtools.common.options.OptionsParsingException; import java.util.Collection; import java.util.List; import javax.annotation.Nullable; /** * Intended drop-in replacement for AnalysisPhaseRunner after we're done with merging Skyframe's * analysis and execution phases. This is part of https://github.com/bazelbuild/bazel/issues/14057. * Internal: b/147350683. * *

TODO(leba): Consider removing this class altogether to reduce complexity. */ public final class AnalysisAndExecutionPhaseRunner { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); private AnalysisAndExecutionPhaseRunner() {} @Nullable static AnalysisAndExecutionResult execute( CommandEnvironment env, BuildRequest request, BuildOptions buildOptions, TargetPatternPhaseValue loadingResult, ExecutionSetup executionSetupCallback, BuildConfigurationsCreated buildConfigurationCreatedCallback, BuildDriverKeyTestContext buildDriverKeyTestContext) throws BuildFailedException, InterruptedException, ViewCreationFailedException, AbruptExitException, InvalidConfigurationException, TestExecException, RepositoryMappingResolutionException { // Compute the heuristic instrumentation filter if needed. if (request.needsInstrumentationFilter()) { try (SilentCloseable c = Profiler.instance().profile(""Compute instrumentation filter"")) { String instrumentationFilter = InstrumentationFilterSupport.computeInstrumentationFilter( env.getReporter(), // TODO(ulfjack): Expensive. Make this part of the TargetPatternPhaseValue or write // a new SkyFunction to compute it? loadingResult.getTestsToRun(env.getReporter(), env.getPackageManager())); try { // We're modifying the buildOptions in place, which is not ideal, but we also don't want // to pay the price for making a copy. Maybe reconsider later if this turns out to be a // problem (and the performance loss may not be a big deal). buildOptions.get(CoreOptions.class).instrumentationFilter = new RegexFilter.RegexFilterConverter().convert(instrumentationFilter); } catch (OptionsParsingException e) { throw new InvalidConfigurationException(Code.HEURISTIC_INSTRUMENTATION_FILTER_INVALID, e); } } } // Exit if there are any pending exceptions from modules. env.throwPendingException(); AnalysisAndExecutionResult analysisAndExecutionResult = null; if (request.getBuildOptions().performAnalysisPhase) { Profiler.instance().markPhase(ProfilePhase.ANALYZE_AND_EXECUTE); // The build info factories are immutable during the life time of this server. However, we // sometimes clean the graph, which requires re-injecting the value, which requires a hook to // do so afterwards, and there is no such hook at the server / workspace level right now. For // simplicity, we keep the code here for now. env.getSkyframeExecutor() .injectExtraPrecomputedValues( ImmutableList.of( PrecomputedValue.injected( BuildInfoCollectionFunction.BUILD_INFO_FACTORIES, env.getRuntime().getRuleClassProvider().getBuildInfoFactoriesAsMap()))); try (SilentCloseable c = Profiler.instance().profile(""runAnalysisAndExecutionPhase""); TopLevelTargetAnalysisWatcher watcher = TopLevelTargetAnalysisWatcher.createAndRegisterWithEventBus( env.getRuntime().getBlazeModules(), env, request, buildOptions)) { analysisAndExecutionResult = runAnalysisAndExecutionPhase( env, request, loadingResult, buildOptions, executionSetupCallback, buildConfigurationCreatedCallback, buildDriverKeyTestContext); } BuildResultListener buildResultListener = env.getBuildResultListener(); if (request.shouldRunTests()) { AnalysisPhaseRunner.reportTargetsWithTests( env, buildResultListener.getAnalyzedTargets(), buildResultListener.getAnalyzedTests()); } else { AnalysisPhaseRunner.reportTargets(env, buildResultListener.getAnalyzedTargets()); } AnalysisPhaseRunner.postAbortedEventsForSkippedTargets( env, buildResultListener.getSkippedTargets()); } else { env.getReporter().handle(Event.progress(""Loading complete."")); env.getReporter().post(new NoAnalyzeEvent()); logger.atInfo().log(""No analysis requested, so finished""); FailureDetail failureDetail = BuildView.createAnalysisFailureDetail(loadingResult, /* skyframeAnalysisResult= */ null); if (failureDetail != null) { throw new BuildFailedException( failureDetail.getMessage(), DetailedExitCode.of(failureDetail)); } } return analysisAndExecutionResult; } static TargetPatternPhaseValue evaluateTargetPatterns( CommandEnvironment env, final BuildRequest request, final TargetValidator validator) throws LoadingFailedException, TargetParsingException, InterruptedException { boolean keepGoing = request.getKeepGoing(); TargetPatternPhaseValue result = env.getSkyframeExecutor() .loadTargetPatternsWithFilters( env.getReporter(), request.getTargets(), env.getRelativeWorkingDirectory(), request.getLoadingOptions(), request.getLoadingPhaseThreadCount(), keepGoing, request.shouldRunTests()); if (validator != null) { Collection targets = result.getTargets(env.getReporter(), env.getSkyframeExecutor().getPackageManager()); validator.validateTargets(targets, keepGoing); } return result; } /** * Performs all phases of the build: Setup, Loading, Analysis & Execution. * *

Postcondition: On success, populates the BuildRequest's set of targets to build. * * @return null if the build were successful; a useful error message if errors were encountered * and request.keepGoing. * @throws InterruptedException if the current thread was interrupted. * @throws ViewCreationFailedException if analysis failed for any reason. * @throws InvalidConfigurationException if the configuration can't be determined. * @throws BuildFailedException if action execution failed. * @throws TestExecException if test execution failed. */ private static AnalysisAndExecutionResult runAnalysisAndExecutionPhase( CommandEnvironment env, BuildRequest request, TargetPatternPhaseValue loadingResult, BuildOptions targetOptions, ExecutionSetup executionSetupCallback, BuildConfigurationsCreated buildConfigurationCreatedCallback, BuildDriverKeyTestContext buildDriverKeyTestContext) throws InterruptedException, InvalidConfigurationException, ViewCreationFailedException, BuildFailedException, TestExecException, RepositoryMappingResolutionException, AbruptExitException { env.getReporter().handle(Event.progress(""Loading complete. Analyzing..."")); ImmutableSet

Globbing targets like "":all"" and ""..."" are ignored here and will not be in the returned set. * * @param env the action's environment. * @param requestedTargetPatterns the list of target patterns specified on the command line. * @param keepGoing --keep_going command line option * @param [MASK] no of threads to be used in execution * @return the set of stringified labels of target patterns that represent single targets. The * stringified labels are in the ""unambiguous canonical form"". * @throws ViewCreationFailedException if a pattern fails to parse for some reason. */ private static ImmutableSet

UNIX newlines are assumed (LF). Carriage returns are always ignored. */ private void newline() { if (openParenStackDepth > 0) { newlineInsideExpression(); // in an expression: ignore space } else { checkIndentation = true; setToken(TokenKind.NEWLINE, pos - 1, pos); } } private void newlineInsideExpression() { while (pos < buffer.length) { switch (buffer[pos]) { case ' ': case '\t': case '\r': pos++; break; default: return; } } } /** Computes indentation (updates dent) and advances pos. */ private void computeIndentation() { // we're in a stmt: suck up space at beginning of next line int indentLen = 0; while (pos < buffer.length) { char c = buffer[pos]; if (c == ' ') { indentLen++; pos++; } else if (c == '\r') { pos++; } else if (c == '\t') { indentLen++; pos++; error(""Tab characters are not allowed for indentation. Use spaces instead."", pos); } else if (c == '\n') { // entirely blank line: discard indentLen = 0; pos++; } else if (c == '#') { // line containing only indented comment int oldPos = pos; while (pos < buffer.length && c != '\n') { c = buffer[pos++]; } addComment(oldPos, pos - 1); indentLen = 0; } else { // printing character break; } } if (pos == buffer.length) { indentLen = 0; } // trailing space on last line int [MASK] = indentStack.peek(); if ( [MASK] < indentLen) { // push a level indentStack.push(indentLen); dents++; } else if ( [MASK] > indentLen) { // pop one or more levels while ( [MASK] > indentLen) { indentStack.pop(); dents--; [MASK] = indentStack.peek(); } if ( [MASK] < indentLen) { error(""indentation error"", pos - 1); } } } /** * Returns true if current position is in the middle of a triple quote * delimiter (3 x quot), and advances 'pos' by two if so. */ private boolean skipTripleQuote(char quot) { if (peek(0) == quot && peek(1) == quot) { pos += 2; return true; } else { return false; } } /** * Scans a string literal delimited by 'quot', containing escape sequences. * *

ON ENTRY: 'pos' is 1 + the index of the first delimiter * ON EXIT: 'pos' is 1 + the index of the last delimiter. */ private void escapedStringLiteral(char quot, boolean isRaw) { int literalStartPos = isRaw ? pos - 2 : pos - 1; boolean inTriplequote = skipTripleQuote(quot); // more expensive second choice that expands escaped into a buffer StringBuilder literal = new StringBuilder(); while (pos < buffer.length) { char c = buffer[pos]; pos++; switch (c) { case '\n': if (inTriplequote) { literal.append(c); break; } else { error(""unclosed string literal"", literalStartPos); setToken(TokenKind.STRING, literalStartPos, pos); setValue(literal.toString()); return; } case '\\': if (pos == buffer.length) { error(""unclosed string literal"", literalStartPos); setToken(TokenKind.STRING, literalStartPos, pos); setValue(literal.toString()); return; } if (isRaw) { // Insert \ and the following character. // As in Python, it means that a raw string can never end with a single \. literal.append('\\'); if (peek(0) == '\r' && peek(1) == '\n') { literal.append(""\n""); pos += 2; } else if (buffer[pos] == '\r' || buffer[pos] == '\n') { literal.append(""\n""); pos += 1; } else { literal.append(buffer[pos]); pos += 1; } break; } c = buffer[pos]; pos++; switch (c) { case '\r': if (peek(0) == '\n') { pos += 1; break; } else { break; } case '\n': // ignore end of line character break; case 'a': literal.append('\u0007'); break; case 'b': literal.append('\b'); break; case 'f': literal.append('\f'); break; case 'n': literal.append('\n'); break; case 'r': literal.append('\r'); break; case 't': literal.append('\t'); break; case 'v': literal.append('\u000b'); break; case '\\': literal.append('\\'); break; case '\'': literal.append('\''); break; case '""': literal.append('""'); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { // octal escape int octal = c - '0'; if (pos < buffer.length) { c = buffer[pos]; if (c >= '0' && c <= '7') { pos++; octal = (octal << 3) | (c - '0'); if (pos < buffer.length) { c = buffer[pos]; if (c >= '0' && c <= '7') { pos++; octal = (octal << 3) | (c - '0'); } } } } if (octal > 0xff) { error(""octal escape sequence out of range (maximum is \\377)"", pos - 1); } else if (options.stringLiteralsAreAsciiOnly() && octal >= 0x80) { error(""octal escape sequence denotes non-ASCII character"", pos - 1); } literal.append((char) (octal & 0xff)); break; } case 'N': case 'u': case 'U': default: // unknown char escape => ""\literal"" error(""invalid escape sequence: \\"" + c + "". Use '\\\\' to insert '\\'."", pos - 1); literal.append('\\'); literal.append(c); break; } break; case '\'': case '""': if (c != quot || (inTriplequote && !skipTripleQuote(quot))) { // Non-matching quote, treat it like a regular char. literal.append(c); } else { // Matching close-delimiter, all done. setToken(TokenKind.STRING, literalStartPos, pos); setValue(literal.toString()); return; } break; default: literal.append(c); if (options.stringLiteralsAreAsciiOnly() && c >= 0x80) { error(""string literal contains non-ASCII character"", pos - 1); } break; } } error(""unclosed string literal"", literalStartPos); setToken(TokenKind.STRING, literalStartPos, pos); setValue(literal.toString()); } /** * Scans a string literal delimited by 'quot'. * *

    *
  • ON ENTRY: 'pos' is 1 + the index of the first delimiter *
  • ON EXIT: 'pos' is 1 + the index of the last delimiter. *
* * @param isRaw if true, do not escape the string. */ private void stringLiteral(char quot, boolean isRaw) { int literalStartPos = isRaw ? pos - 2 : pos - 1; int contentStartPos = pos; // Don't even attempt to parse triple-quotes here. if (skipTripleQuote(quot)) { pos -= 2; escapedStringLiteral(quot, isRaw); return; } // first quick optimistic scan for a simple non-escaped string while (pos < buffer.length) { char c = buffer[pos++]; switch (c) { case '\n': error(""unclosed string literal"", literalStartPos); setToken(TokenKind.STRING, literalStartPos, pos); setValue(bufferSlice(contentStartPos, pos - 1)); return; case '\\': if (isRaw) { if (peek(0) == '\r' && peek(1) == '\n') { // There was a CRLF after the newline. No shortcut possible, since it needs to be // transformed into a single LF. pos = contentStartPos; escapedStringLiteral(quot, true); return; } else { pos++; break; } } // oops, hit an escape, need to start over & build a new string buffer pos = contentStartPos; escapedStringLiteral(quot, false); return; case '\'': case '""': if (c == quot) { // close-quote, all done. setToken(TokenKind.STRING, literalStartPos, pos); setValue(bufferSlice(contentStartPos, pos - 1)); // If we're requiring ASCII-only, do another scan for validation. if (options.stringLiteralsAreAsciiOnly()) { for (int i = contentStartPos; i < pos - 1; i++) { if (buffer[i] >= 0x80) { // Can report multiple errors per string literal. error(""string literal contains non-ASCII character"", i); } } } return; } break; default: // fall out } } // If the current position is beyond the end of the file, need to move it backwards // Possible if the file ends with `r""\` (unclosed raw string literal with a backslash) if (pos > buffer.length) { pos = buffer.length; } error(""unclosed string literal"", literalStartPos); setToken(TokenKind.STRING, literalStartPos, pos); setValue(bufferSlice(contentStartPos, pos)); } private static final Map keywordMap = new HashMap<>(); static { keywordMap.put(""and"", TokenKind.AND); keywordMap.put(""as"", TokenKind.AS); keywordMap.put(""assert"", TokenKind.ASSERT); keywordMap.put(""break"", TokenKind.BREAK); keywordMap.put(""class"", TokenKind.CLASS); keywordMap.put(""continue"", TokenKind.CONTINUE); keywordMap.put(""def"", TokenKind.DEF); keywordMap.put(""del"", TokenKind.DEL); keywordMap.put(""elif"", TokenKind.ELIF); keywordMap.put(""else"", TokenKind.ELSE); keywordMap.put(""except"", TokenKind.EXCEPT); keywordMap.put(""finally"", TokenKind.FINALLY); keywordMap.put(""for"", TokenKind.FOR); keywordMap.put(""from"", TokenKind.FROM); keywordMap.put(""global"", TokenKind.GLOBAL); keywordMap.put(""if"", TokenKind.IF); keywordMap.put(""import"", TokenKind.IMPORT); keywordMap.put(""in"", TokenKind.IN); keywordMap.put(""is"", TokenKind.IS); keywordMap.put(""lambda"", TokenKind.LAMBDA); keywordMap.put(""load"", TokenKind.LOAD); keywordMap.put(""nonlocal"", TokenKind.NONLOCAL); keywordMap.put(""not"", TokenKind.NOT); keywordMap.put(""or"", TokenKind.OR); keywordMap.put(""pass"", TokenKind.PASS); keywordMap.put(""raise"", TokenKind.RAISE); keywordMap.put(""return"", TokenKind.RETURN); keywordMap.put(""try"", TokenKind.TRY); keywordMap.put(""while"", TokenKind.WHILE); keywordMap.put(""with"", TokenKind.WITH); keywordMap.put(""yield"", TokenKind.YIELD); } /** * Scans an identifier or keyword. * *

ON ENTRY: 'pos' is 1 + the index of the first char in the identifier. * ON EXIT: 'pos' is 1 + the index of the last char in the identifier. */ private void identifierOrKeyword() { int oldPos = pos - 1; String id = identInterner.intern(scanIdentifier()); TokenKind kind = keywordMap.get(id); if (kind == null) { setToken(TokenKind.IDENTIFIER, oldPos, pos); // setValue allocates a new String for the raw text, but it's not retained so we don't bother // interning it. setValue(id); } else { setToken(kind, oldPos, pos); } } private String scanIdentifier() { // Keep consistent with Identifier.isValid. // TODO(laurentlb): Handle Unicode letters. int oldPos = pos - 1; while (pos < buffer.length) { switch (buffer[pos]) { case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': pos++; break; default: return bufferSlice(oldPos, pos); } } return bufferSlice(oldPos, pos); } /** * Tokenizes a two-char operator. * @return true if it tokenized an operator */ private boolean tokenizeTwoChars() { if (pos + 2 >= buffer.length) { return false; } char c1 = buffer[pos]; char c2 = buffer[pos + 1]; TokenKind tok = null; if (c2 == '=') { tok = EQUAL_TOKENS.get(c1); } else if (c2 == '*' && c1 == '*') { tok = TokenKind.STAR_STAR; } if (tok == null) { return false; } else { setToken(tok, pos, pos + 2); return true; } } // Returns the ith unconsumed char, or -1 for EOF. private int peek(int i) { return pos + i < buffer.length ? buffer[pos + i] : -1; } // Consumes a char and returns the next unconsumed char, or -1 for EOF. private int next() { pos++; return peek(0); } /** * Performs tokenization of the character buffer of file contents provided to the constructor. At * least one token will be added to the tokens queue. */ private void tokenize() { if (checkIndentation) { checkIndentation = false; computeIndentation(); } // Return saved indentation tokens. if (dents != 0) { if (dents < 0) { dents++; setToken(TokenKind.OUTDENT, pos - 1, pos); } else { dents--; setToken(TokenKind.INDENT, pos - 1, pos); } return; } // TODO(adonovan): cleanup: replace break after setToken with return, // and eliminate null-check of this.kind. kind = null; while (pos < buffer.length) { if (tokenizeTwoChars()) { pos += 2; return; } char c = buffer[pos]; pos++; switch (c) { case '{': setToken(TokenKind.LBRACE, pos - 1, pos); openParenStackDepth++; break; case '}': setToken(TokenKind.RBRACE, pos - 1, pos); popParen(); break; case '(': setToken(TokenKind.LPAREN, pos - 1, pos); openParenStackDepth++; break; case ')': setToken(TokenKind.RPAREN, pos - 1, pos); popParen(); break; case '[': setToken(TokenKind.LBRACKET, pos - 1, pos); openParenStackDepth++; break; case ']': setToken(TokenKind.RBRACKET, pos - 1, pos); popParen(); break; case '>': if (peek(0) == '>' && peek(1) == '=') { setToken(TokenKind.GREATER_GREATER_EQUALS, pos - 1, pos + 2); pos += 2; } else if (peek(0) == '>') { setToken(TokenKind.GREATER_GREATER, pos - 1, pos + 1); pos += 1; } else { setToken(TokenKind.GREATER, pos - 1, pos); } break; case '<': if (peek(0) == '<' && peek(1) == '=') { setToken(TokenKind.LESS_LESS_EQUALS, pos - 1, pos + 2); pos += 2; } else if (peek(0) == '<') { setToken(TokenKind.LESS_LESS, pos - 1, pos + 1); pos += 1; } else { setToken(TokenKind.LESS, pos - 1, pos); } break; case ':': setToken(TokenKind.COLON, pos - 1, pos); break; case ',': setToken(TokenKind.COMMA, pos - 1, pos); break; case '+': setToken(TokenKind.PLUS, pos - 1, pos); break; case '-': setToken(TokenKind.MINUS, pos - 1, pos); break; case '|': setToken(TokenKind.PIPE, pos - 1, pos); break; case '=': setToken(TokenKind.EQUALS, pos - 1, pos); break; case '%': setToken(TokenKind.PERCENT, pos - 1, pos); break; case '~': setToken(TokenKind.TILDE, pos - 1, pos); break; case '&': setToken(TokenKind.AMPERSAND, pos - 1, pos); break; case '^': setToken(TokenKind.CARET, pos - 1, pos); break; case '/': if (peek(0) == '/' && peek(1) == '=') { setToken(TokenKind.SLASH_SLASH_EQUALS, pos - 1, pos + 2); pos += 2; } else if (peek(0) == '/') { setToken(TokenKind.SLASH_SLASH, pos - 1, pos + 1); pos += 1; } else { // /= is handled by tokenizeTwoChars. setToken(TokenKind.SLASH, pos - 1, pos); } break; case ';': setToken(TokenKind.SEMI, pos - 1, pos); break; case '*': setToken(TokenKind.STAR, pos - 1, pos); break; case ' ': case '\t': case '\r': /* ignore */ break; case '\\': // Backslash character is valid only at the end of a line (or in a string) if (peek(0) == '\n') { pos += 1; // skip the end of line character } else if (peek(0) == '\r' && peek(1) == '\n') { pos += 2; // skip the CRLF at the end of line } else { setToken(TokenKind.ILLEGAL, pos - 1, pos); setValue(Character.toString(c)); } break; case '\n': newline(); break; case '#': int oldPos = pos - 1; while (pos < buffer.length) { c = buffer[pos]; if (c == '\n') { break; } else { pos++; } } addComment(oldPos, pos); break; case '\'': case '\""': stringLiteral(c, false); break; default: // detect raw strings, e.g. r""str"" if (c == 'r') { int c0 = peek(0); if (c0 == '\'' || c0 == '\""') { pos++; stringLiteral((char) c0, true); break; } } // int or float literal, or dot if (c == '.' || isdigit(c)) { pos--; // unconsume scanNumberOrDot(c); break; } if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { identifierOrKeyword(); } else { error(""invalid character: '"" + c + ""'"", pos - 1); } break; } // switch if (kind != null) { // stop here if we scanned a token return; } } // while if (indentStack.size() > 1) { // top of stack is always zero setToken(TokenKind.NEWLINE, pos - 1, pos); while (indentStack.size() > 1) { indentStack.pop(); dents--; } return; } setToken(TokenKind.EOF, pos, pos); } // Scans a number (INT or FLOAT) or DOT. // Precondition: c == peek(0) (a dot or digit) // // TODO(adonovan): make this the precondition for all scan functions; // currenly most assume their argument c has been consumed already. private void scanNumberOrDot(int c) { int start = this.pos; boolean fraction = false; boolean exponent = false; if (c == '.') { // dot or start of fraction if (!isdigit(peek(1))) { pos++; // consume '.' setToken(TokenKind.DOT, start, pos); return; } fraction = true; } else if (c == '0') { // hex, octal, binary or float c = next(); if (c == '.') { fraction = true; } else if (c == 'x' || c == 'X') { // hex c = next(); if (!isxdigit(c)) { error(""invalid hex literal"", start); } while (isxdigit(c)) { c = next(); } } else if (c == 'o' || c == 'O') { // octal c = next(); while (isdigit(c)) { c = next(); } } else if (c == 'b' || c == 'B') { // binary c = next(); if (!isbdigit(c)) { error(""invalid binary literal"", start); } while (isbdigit(c)) { c = next(); } } else { // ""0"" or float or obsolete octal ""0755"" while (isdigit(c)) { c = next(); } if (c == '.') { fraction = true; } else if (c == 'e' || c == 'E') { exponent = true; } } } else { // decimal while (isdigit(c)) { c = next(); } if (c == '.') { fraction = true; } else if (c == 'e' || c == 'E') { exponent = true; } } if (fraction) { c = next(); // consume '.' while (isdigit(c)) { c = next(); } if (c == 'e' || c == 'E') { exponent = true; } } if (exponent) { c = next(); // consume [eE] if (c == '+' || c == '-') { c = next(); } while (isdigit(c)) { c = next(); } } // float? if (fraction || exponent) { setToken(TokenKind.FLOAT, start, pos); double value = 0.0; try { value = Double.parseDouble(bufferSlice(start, pos)); if (!Double.isFinite(value)) { error(""floating-point literal too large"", start); } } catch (NumberFormatException ex) { error(""invalid float literal"", start); } setValue(value); return; } // int setToken(TokenKind.INT, start, pos); String literal = bufferSlice(start, pos); Number value = 0; try { value = IntLiteral.scan(literal); } catch (NumberFormatException ex) { error(ex.getMessage(), start); } setValue(value); } private static boolean isdigit(int c) { return '0' <= c && c <= '9'; } private static boolean isxdigit(int c) { return isdigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'); } private static boolean isbdigit(int c) { return c == '0' || c == '1'; } /* * Returns a string containing the part of the source buffer beginning at offset {@code start} and * ending immediately before offset {@code end} (so the length of the resulting string is {@code * end - start}). */ String bufferSlice(int start, int end) { return new String(this.buffer, start, end - start); } // TODO(adonovan): don't retain comments unconditionally. private void addComment(int start, int end) { String content = bufferSlice(start, end); comments.add(new Comment(locs, start, content)); } } ","peekedIndent " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static java.lang.Math.max; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer.InsufficientCapacityException; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.source.SampleStream.ReadFlags; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MediaClock; import java.io.IOException; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * An abstract base class suitable for most {@link Renderer} implementations. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public abstract class BaseRenderer implements Renderer, RendererCapabilities { private final Object lock; private final @C.TrackType int trackType; private final FormatHolder formatHolder; @Nullable private RendererConfiguration configuration; private int index; private @MonotonicNonNull PlayerId playerId; private int state; @Nullable private SampleStream stream; @Nullable private Format[] streamFormats; private long streamOffsetUs; private long lastResetPositionUs; private long readingPositionUs; private boolean streamIsFinal; private boolean throwRendererExceptionIsExecuting; @GuardedBy(""lock"") @Nullable private RendererCapabilities.Listener rendererCapabilitiesListener; /** * @param trackType The track type that the renderer handles. One of the {@link C} {@code * TRACK_TYPE_*} constants. */ public BaseRenderer(@C.TrackType int trackType) { lock = new Object(); this.trackType = trackType; formatHolder = new FormatHolder(); readingPositionUs = C.TIME_END_OF_SOURCE; } @Override public final @C.TrackType int getTrackType() { return trackType; } @Override public final RendererCapabilities getCapabilities() { return this; } @Override public final void init(int index, PlayerId playerId) { this.index = index; this.playerId = playerId; } @Override @Nullable public MediaClock getMediaClock() { return null; } @Override public final int getState() { return state; } @Override public final void enable( RendererConfiguration configuration, Format[] formats, SampleStream stream, long positionUs, boolean joining, boolean mayRenderStartOfStream, long startPositionUs, long offsetUs) throws ExoPlaybackException { Assertions.checkState(state == STATE_DISABLED); this.configuration = configuration; state = STATE_ENABLED; onEnabled(joining, mayRenderStartOfStream); replaceStream(formats, stream, startPositionUs, offsetUs); resetPosition(positionUs, joining); } @Override public final void start() throws ExoPlaybackException { Assertions.checkState(state == STATE_ENABLED); state = STATE_STARTED; onStarted(); } @Override public final void replaceStream( Format[] formats, SampleStream stream, long startPositionUs, long offsetUs) throws ExoPlaybackException { Assertions.checkState(!streamIsFinal); this.stream = stream; if (readingPositionUs == C.TIME_END_OF_SOURCE) { readingPositionUs = startPositionUs; } streamFormats = formats; streamOffsetUs = offsetUs; onStreamChanged(formats, startPositionUs, offsetUs); } @Override @Nullable public final SampleStream getStream() { return stream; } @Override public final boolean hasReadStreamToEnd() { return readingPositionUs == C.TIME_END_OF_SOURCE; } @Override public final long getReadingPositionUs() { return readingPositionUs; } @Override public final void setCurrentStreamFinal() { streamIsFinal = true; } @Override public final boolean isCurrentStreamFinal() { return streamIsFinal; } @Override public final void maybeThrowStreamError() throws IOException { Assertions.checkNotNull(stream).maybeThrowError(); } @Override public final void resetPosition(long positionUs) throws ExoPlaybackException { resetPosition(positionUs, /* joining= */ false); } private void resetPosition(long positionUs, boolean joining) throws ExoPlaybackException { streamIsFinal = false; lastResetPositionUs = positionUs; readingPositionUs = positionUs; onPositionReset(positionUs, joining); } @Override public final void stop() { Assertions.checkState(state == STATE_STARTED); state = STATE_ENABLED; onStopped(); } @Override public final void disable() { Assertions.checkState(state == STATE_ENABLED); formatHolder.clear(); state = STATE_DISABLED; stream = null; streamFormats = null; streamIsFinal = false; onDisabled(); } @Override public final void reset() { Assertions.checkState(state == STATE_DISABLED); formatHolder.clear(); onReset(); } @Override public final void release() { Assertions.checkState(state == STATE_DISABLED); onRelease(); } // RendererCapabilities implementation. @Override public @AdaptiveSupport int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException { return ADAPTIVE_NOT_SUPPORTED; } @Override public final void setListener(RendererCapabilities.Listener listener) { synchronized (lock) { this.rendererCapabilitiesListener = listener; } } @Override public final void clearListener() { synchronized (lock) { this.rendererCapabilitiesListener = null; } } // PlayerMessage.Target implementation. @Override public void handleMessage(@MessageType int messageType, @Nullable Object message) throws ExoPlaybackException { // Do nothing. } // Methods to be overridden by subclasses. /** * Called when the renderer is enabled. * *

The default implementation is a no-op. * * @param joining Whether this renderer is being enabled to join an ongoing playback. * @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the * stream even if the state is not {@link #STATE_STARTED} yet. * @throws ExoPlaybackException If an error occurs. */ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) throws ExoPlaybackException { // Do nothing. } /** * Called when the renderer's stream has changed. This occurs when the renderer is enabled after * {@link #onEnabled(boolean, boolean)} has been called, and also when the stream has been * replaced whilst the renderer is enabled or started. * *

The default implementation is a no-op. * * @param formats The enabled formats. * @param startPositionUs The start position of the new stream in renderer time (microseconds). * @param offsetUs The offset that will be added to the timestamps of buffers read via {@link * #readSource} so that decoder input buffers have monotonically increasing timestamps. * @throws ExoPlaybackException If an error occurs. */ protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) throws ExoPlaybackException { // Do nothing. } /** * Called when the position is reset. This occurs when the renderer is enabled after {@link * #onStreamChanged(Format[], long, long)} has been called, and also when a position discontinuity * is encountered. * *

After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples * starting from a key frame. * *

The default implementation is a no-op. * * @param positionUs The new playback position in microseconds. * @param joining Whether this renderer is being enabled to join an ongoing playback. * @throws ExoPlaybackException If an error occurs. */ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { // Do nothing. } /** * Called when the renderer is started. * *

The default implementation is a no-op. * * @throws ExoPlaybackException If an error occurs. */ protected void onStarted() throws ExoPlaybackException { // Do nothing. } /** * Called when the renderer is stopped. * *

The default implementation is a no-op. */ protected void onStopped() { // Do nothing. } /** * Called when the renderer is disabled. * *

The default implementation is a no-op. */ protected void onDisabled() { // Do nothing. } /** * Called when the renderer is reset. * *

The default implementation is a no-op. */ protected void onReset() { // Do nothing. } /** * Called when the renderer is released. * *

The default implementation is a no-op. */ protected void onRelease() { // Do nothing. } // Methods to be called by subclasses. /** * Returns the position passed to the most recent call to {@link #enable} or {@link * #resetPosition}. */ protected final long getLastResetPositionUs() { return lastResetPositionUs; } /** Returns a clear {@link FormatHolder}. */ protected final FormatHolder getFormatHolder() { formatHolder.clear(); return formatHolder; } /** * Returns the formats of the currently enabled stream. * *

This method may be called when the renderer is in the following states: {@link * #STATE_ENABLED}, {@link #STATE_STARTED}. */ protected final Format[] getStreamFormats() { return Assertions.checkNotNull(streamFormats); } /** * Returns the configuration set when the renderer was most recently enabled. * *

This method may be called when the renderer is in the following states: {@link * #STATE_ENABLED}, {@link #STATE_STARTED}. */ protected final RendererConfiguration getConfiguration() { return Assertions.checkNotNull(configuration); } /** * Returns the index of the renderer within the player. * *

Must only be used after the renderer has been initialized by the player. */ protected final int getIndex() { return index; } /** * Returns the {@link PlayerId} of the player using this renderer. * *

Must only be used after the renderer has been initialized by the player. */ protected final PlayerId getPlayerId() { return checkNotNull(playerId); } /** * Creates an {@link ExoPlaybackException} of type {@link ExoPlaybackException#TYPE_RENDERER} for * this renderer. * * @param cause The cause of the exception. * @param format The current format used by the renderer. May be null. * @param errorCode A {@link PlaybackException.ErrorCode} to identify the cause of the playback * failure. * @return The created instance, in which {@link ExoPlaybackException#isRecoverable} is {@code * false}. */ protected final ExoPlaybackException createRendererException( Throwable cause, @Nullable Format format, @PlaybackException.ErrorCode int errorCode) { return createRendererException(cause, format, /* isRecoverable= */ false, errorCode); } /** * Creates an {@link ExoPlaybackException} of type {@link ExoPlaybackException#TYPE_RENDERER} for * this renderer. * * @param cause The cause of the exception. * @param format The current format used by the renderer. May be null. * @param isRecoverable If the error is recoverable by disabling and re-enabling the renderer. * @param errorCode A {@link PlaybackException.ErrorCode} to identify the cause of the playback * failure. * @return The created instance. */ protected final ExoPlaybackException createRendererException( Throwable cause, @Nullable Format format, boolean isRecoverable, @PlaybackException.ErrorCode int errorCode) { @C.FormatSupport int formatSupport = C.FORMAT_HANDLED; if (format != null && !throwRendererExceptionIsExecuting) { // Prevent recursive re-entry from subclass supportsFormat implementations. throwRendererExceptionIsExecuting = true; try { formatSupport = RendererCapabilities.getFormatSupport(supportsFormat(format)); } catch (ExoPlaybackException e) { // Ignore, we are already failing. } finally { throwRendererExceptionIsExecuting = false; } } return ExoPlaybackException.createForRenderer( cause, getName(), getIndex(), format, formatSupport, isRecoverable, errorCode); } /** * Reads from the enabled upstream source. If the upstream source has been read to the end then * {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamFinal()} has been * called. {@link C#RESULT_NOTHING_READ} is returned otherwise. * *

This method may be called when the renderer is in the following states: {@link * #STATE_ENABLED}, {@link #STATE_STARTED}. * * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the * end of the stream. If the end of the stream has been reached, the {@link * C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. * @param [MASK] Flags controlling the behavior of this read operation. * @return The {@link ReadDataResult result} of the read operation. * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold * the data of a sample being read. The buffer {@link DecoderInputBuffer#timeUs timestamp} and * flags are populated if this exception is thrown, but the read position is not advanced. */ protected final @ReadDataResult int readSource( FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int [MASK] ) { @ReadDataResult int result = Assertions.checkNotNull(stream).readData(formatHolder, buffer, [MASK] ); if (result == C.RESULT_BUFFER_READ) { if (buffer.isEndOfStream()) { readingPositionUs = C.TIME_END_OF_SOURCE; return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ; } buffer.timeUs += streamOffsetUs; readingPositionUs = max(readingPositionUs, buffer.timeUs); } else if (result == C.RESULT_FORMAT_READ) { Format format = Assertions.checkNotNull(formatHolder.format); if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { format = format .buildUpon() .setSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs) .build(); formatHolder.format = format; } } return result; } /** * Attempts to skip to the keyframe before the specified position, or to the end of the stream if * {@code positionUs} is beyond it. * *

This method may be called when the renderer is in the following states: {@link * #STATE_ENABLED}, {@link #STATE_STARTED}. * * @param positionUs The position in microseconds. * @return The number of samples that were skipped. */ protected int skipSource(long positionUs) { return Assertions.checkNotNull(stream).skipData(positionUs - streamOffsetUs); } /** * Returns whether the upstream source is ready. * *

This method may be called when the renderer is in the following states: {@link * #STATE_ENABLED}, {@link #STATE_STARTED}. */ protected final boolean isSourceReady() { return hasReadStreamToEnd() ? streamIsFinal : Assertions.checkNotNull(stream).isReady(); } /** Called when the renderer capabilities are changed. */ protected final void onRendererCapabilitiesChanged() { @Nullable RendererCapabilities.Listener listener; synchronized (lock) { listener = rendererCapabilitiesListener; } if (listener != null) { listener.onRendererCapabilitiesChanged(this); } } } ","readFlags " "package com.alibaba.fastjson.support. [MASK] ; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import okhttp3.MediaType; import okhttp3.RequestBody; import okhttp3.ResponseBody; import [MASK] 2.Converter; import [MASK] 2.Retrofit; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; /** * @author ligboy, wenshao * @author Victor.Zxy */ public class Retrofit2ConverterFactory extends Converter.Factory { private static final MediaType MEDIA_TYPE = MediaType.parse(""application/json; charset=UTF-8""); private FastJsonConfig fastJsonConfig; @Deprecated private static final Feature[] EMPTY_SERIALIZER_FEATURES = new Feature[0]; @Deprecated private ParserConfig parserConfig = ParserConfig.getGlobalInstance(); @Deprecated private int featureValues = JSON.DEFAULT_PARSER_FEATURE; @Deprecated private Feature[] features; @Deprecated private SerializeConfig serializeConfig; @Deprecated private SerializerFeature[] serializerFeatures; public Retrofit2ConverterFactory() { this.fastJsonConfig = new FastJsonConfig(); } public Retrofit2ConverterFactory(FastJsonConfig fastJsonConfig) { this.fastJsonConfig = fastJsonConfig; } public static Retrofit2ConverterFactory create() { return create(new FastJsonConfig()); } public static Retrofit2ConverterFactory create(FastJsonConfig fastJsonConfig) { if (fastJsonConfig == null) throw new NullPointerException(""fastJsonConfig == null""); return new Retrofit2ConverterFactory(fastJsonConfig); } @Override public Converter responseBodyConverter(Type type, // Annotation[] annotations, // Retrofit [MASK] ) { return new ResponseBodyConverter(type); } @Override public Converter requestBodyConverter(Type type, // Annotation[] parameterAnnotations, // Annotation[] methodAnnotations, // Retrofit [MASK] ) { return new RequestBodyConverter(); } public FastJsonConfig getFastJsonConfig() { return fastJsonConfig; } public Retrofit2ConverterFactory setFastJsonConfig(FastJsonConfig fastJsonConfig) { this.fastJsonConfig = fastJsonConfig; return this; } /** * Gets parser config. * * @return the parser config * @see FastJsonConfig#getParserConfig() * @deprecated */ @Deprecated public ParserConfig getParserConfig() { return fastJsonConfig.getParserConfig(); } /** * Sets parser config. * * @param config the config * @return the parser config * @see FastJsonConfig#setParserConfig(ParserConfig) * @deprecated */ @Deprecated public Retrofit2ConverterFactory setParserConfig(ParserConfig config) { fastJsonConfig.setParserConfig(config); return this; } /** * Gets parser feature values. * * @return the parser feature values * @see JSON#DEFAULT_PARSER_FEATURE * @deprecated */ @Deprecated public int getParserFeatureValues() { return JSON.DEFAULT_PARSER_FEATURE; } /** * Sets parser feature values. * * @param featureValues the feature values * @return the parser feature values * @see JSON#DEFAULT_PARSER_FEATURE * @deprecated */ @Deprecated public Retrofit2ConverterFactory setParserFeatureValues(int featureValues) { return this; } /** * Get parser features feature []. * * @return the feature [] * @see FastJsonConfig#getFeatures() * @deprecated */ @Deprecated public Feature[] getParserFeatures() { return fastJsonConfig.getFeatures(); } /** * Sets parser features. * * @param features the features * @return the parser features * @see FastJsonConfig#setFeatures(Feature...) * @deprecated */ @Deprecated public Retrofit2ConverterFactory setParserFeatures(Feature[] features) { fastJsonConfig.setFeatures(features); return this; } /** * Gets serialize config. * * @return the serialize config * @see FastJsonConfig#getSerializeConfig() * @deprecated */ @Deprecated public SerializeConfig getSerializeConfig() { return fastJsonConfig.getSerializeConfig(); } /** * Sets serialize config. * * @param serializeConfig the serialize config * @return the serialize config * @see FastJsonConfig#setSerializeConfig(SerializeConfig) * @deprecated */ @Deprecated public Retrofit2ConverterFactory setSerializeConfig(SerializeConfig serializeConfig) { fastJsonConfig.setSerializeConfig(serializeConfig); return this; } /** * Get serializer features serializer feature []. * * @return the serializer feature [] * @see FastJsonConfig#getSerializerFeatures() * @deprecated */ @Deprecated public SerializerFeature[] getSerializerFeatures() { return fastJsonConfig.getSerializerFeatures(); } /** * Sets serializer features. * * @param features the features * @return the serializer features * @see FastJsonConfig#setSerializerFeatures(SerializerFeature...) * @deprecated */ @Deprecated public Retrofit2ConverterFactory setSerializerFeatures(SerializerFeature[] features) { fastJsonConfig.setSerializerFeatures(features); return this; } final class ResponseBodyConverter implements Converter { private Type type; ResponseBodyConverter(Type type) { this.type = type; } public T convert(ResponseBody value) throws IOException { try { return JSON.parseObject(value.bytes() , fastJsonConfig.getCharset() , type , fastJsonConfig.getParserConfig() , fastJsonConfig.getParseProcess() , JSON.DEFAULT_PARSER_FEATURE , fastJsonConfig.getFeatures() ); } catch (Exception e) { throw new IOException(""JSON parse error: "" + e.getMessage(), e); } finally { value.close(); } } } final class RequestBodyConverter implements Converter { RequestBodyConverter() { } public RequestBody convert(T value) throws IOException { try { byte[] content = JSON.toJSONBytesWithFastJsonConfig(fastJsonConfig.getCharset() , value , fastJsonConfig.getSerializeConfig() , fastJsonConfig.getSerializeFilters() , fastJsonConfig.getDateFormat() , JSON.DEFAULT_GENERATE_FEATURE , fastJsonConfig.getSerializerFeatures() ); return RequestBody.create(MEDIA_TYPE, content); } catch (Exception e) { throw new IOException(""Could not write JSON: "" + e.getMessage(), e); } } } } ","retrofit " "package com.airbnb.lottie.value; import androidx.annotation.RestrictTo; /** * Data class for use with {@link LottieValueCallback}. * You should *not* hold a reference to the frame info parameter passed to your callback. It will be reused. */ public class LottieFrameInfo { private float startFrame; private float endFrame; private T startValue; private T endValue; private float linearKeyframeProgress; private float interpolatedKeyframeProgress; private float [MASK] ; @RestrictTo(RestrictTo.Scope.LIBRARY) public LottieFrameInfo set( float startFrame, float endFrame, T startValue, T endValue, float linearKeyframeProgress, float interpolatedKeyframeProgress, float [MASK] ) { this.startFrame = startFrame; this.endFrame = endFrame; this.startValue = startValue; this.endValue = endValue; this.linearKeyframeProgress = linearKeyframeProgress; this.interpolatedKeyframeProgress = interpolatedKeyframeProgress; this. [MASK] = [MASK] ; return this; } public float getStartFrame() { return startFrame; } public float getEndFrame() { return endFrame; } public T getStartValue() { return startValue; } public T getEndValue() { return endValue; } public float getLinearKeyframeProgress() { return linearKeyframeProgress; } public float getInterpolatedKeyframeProgress() { return interpolatedKeyframeProgress; } public float getOverallProgress() { return [MASK] ; } } ","overallProgress " "/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.channel.epoll; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.AbstractChannel; import io.netty.channel.Channel; import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelException; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelMetadata; import io.netty.channel.ChannelOutboundBuffer; import io.netty.channel.ChannelPromise; import io.netty.channel.ConnectTimeoutException; import io.netty.channel.EventLoop; import io.netty.channel.RecvByteBufAllocator; import io.netty.channel.socket.ChannelInputShutdownEvent; import io.netty.channel.socket.ChannelInputShutdownReadComplete; import io.netty.channel.socket.SocketChannelConfig; import io.netty.channel.unix.FileDescriptor; import io.netty.channel.unix.IovArray; import io.netty.channel.unix.Socket; import io.netty.channel.unix.UnixChannel; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AlreadyConnectedException; import java.nio.channels.ClosedChannelException; import java.nio.channels.ConnectionPendingException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.UnresolvedAddressException; import java.util.concurrent.TimeUnit; import static io.netty.channel.internal.ChannelUtils.WRITE_STATUS_SNDBUF_FULL; import static io.netty.channel.unix.UnixChannelUtil.computeRemoteAddr; import static io.netty.util.internal.ObjectUtil.checkNotNull; abstract class AbstractEpollChannel extends AbstractChannel implements UnixChannel { private static final ChannelMetadata METADATA = new ChannelMetadata(false); protected final LinuxSocket socket; /** * The [MASK] of the current connection attempt. If not null, subsequent * connection attempts will fail. */ private ChannelPromise connectPromise; private Future connectTimeoutFuture; private SocketAddress requestedRemoteAddress; private volatile SocketAddress local; private volatile SocketAddress remote; protected int flags = Native.EPOLLET; boolean inputClosedSeenErrorOnRead; boolean epollInReadyRunnablePending; protected volatile boolean active; AbstractEpollChannel(LinuxSocket fd) { this(null, fd, false); } AbstractEpollChannel(Channel parent, LinuxSocket fd, boolean active) { super(parent); this.socket = checkNotNull(fd, ""fd""); this.active = active; if (active) { // Directly cache the remote and local addresses // See https://github.com/netty/netty/issues/2359 this.local = fd.localAddress(); this.remote = fd.remoteAddress(); } } AbstractEpollChannel(Channel parent, LinuxSocket fd, SocketAddress remote) { super(parent); this.socket = checkNotNull(fd, ""fd""); this.active = true; // Directly cache the remote and local addresses // See https://github.com/netty/netty/issues/2359 this.remote = remote; this.local = fd.localAddress(); } static boolean isSoErrorZero(Socket fd) { try { return fd.getSoError() == 0; } catch (IOException e) { throw new ChannelException(e); } } protected void setFlag(int flag) throws IOException { if (!isFlagSet(flag)) { flags |= flag; modifyEvents(); } } void clearFlag(int flag) throws IOException { if (isFlagSet(flag)) { flags &= ~flag; modifyEvents(); } } boolean isFlagSet(int flag) { return (flags & flag) != 0; } @Override public final FileDescriptor fd() { return socket; } @Override public abstract EpollChannelConfig config(); @Override public boolean isActive() { return active; } @Override public ChannelMetadata metadata() { return METADATA; } @Override protected void doClose() throws Exception { active = false; // Even if we allow half closed sockets we should give up on reading. Otherwise we may allow a read attempt on a // socket which has not even been connected yet. This has been observed to block during unit tests. inputClosedSeenErrorOnRead = true; try { ChannelPromise promise = connectPromise; if (promise != null) { // Use tryFailure() instead of setFailure() to avoid the race against cancel(). promise.tryFailure(new ClosedChannelException()); connectPromise = null; } Future [MASK] = connectTimeoutFuture; if ( [MASK] != null) { [MASK] .cancel(false); connectTimeoutFuture = null; } if (isRegistered()) { // Need to check if we are on the EventLoop as doClose() may be triggered by the GlobalEventExecutor // if SO_LINGER is used. // // See https://github.com/netty/netty/issues/7159 EventLoop loop = eventLoop(); if (loop.inEventLoop()) { doDeregister(); } else { loop.execute(new Runnable() { @Override public void run() { try { doDeregister(); } catch (Throwable cause) { pipeline().fireExceptionCaught(cause); } } }); } } } finally { socket.close(); } } void resetCachedAddresses() { local = socket.localAddress(); remote = socket.remoteAddress(); } @Override protected void doDisconnect() throws Exception { doClose(); } @Override protected boolean isCompatible(EventLoop loop) { return loop instanceof EpollEventLoop; } @Override public boolean isOpen() { return socket.isOpen(); } @Override protected void doDeregister() throws Exception { ((EpollEventLoop) eventLoop()).remove(this); } @Override protected final void doBeginRead() throws Exception { // Channel.read() or ChannelHandlerContext.read() was called final AbstractEpollUnsafe unsafe = (AbstractEpollUnsafe) unsafe(); unsafe.readPending = true; // We must set the read flag here as it is possible the user didn't read in the last read loop, the // executeEpollInReadyRunnable could read nothing, and if the user doesn't explicitly call read they will // never get data after this. setFlag(Native.EPOLLIN); // If EPOLL ET mode is enabled and auto read was toggled off on the last read loop then we may not be notified // again if we didn't consume all the data. So we force a read operation here if there maybe more data. if (unsafe.maybeMoreDataToRead) { unsafe.executeEpollInReadyRunnable(config()); } } final boolean shouldBreakEpollInReady(ChannelConfig config) { return socket.isInputShutdown() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config)); } private static boolean isAllowHalfClosure(ChannelConfig config) { if (config instanceof EpollDomainSocketChannelConfig) { return ((EpollDomainSocketChannelConfig) config).isAllowHalfClosure(); } return config instanceof SocketChannelConfig && ((SocketChannelConfig) config).isAllowHalfClosure(); } final void clearEpollIn() { // Only clear if registered with an EventLoop as otherwise if (isRegistered()) { final EventLoop loop = eventLoop(); final AbstractEpollUnsafe unsafe = (AbstractEpollUnsafe) unsafe(); if (loop.inEventLoop()) { unsafe.clearEpollIn0(); } else { // schedule a task to clear the EPOLLIN as it is not safe to modify it directly loop.execute(new Runnable() { @Override public void run() { if (!unsafe.readPending && !config().isAutoRead()) { // Still no read triggered so clear it now unsafe.clearEpollIn0(); } } }); } } else { // The EventLoop is not registered atm so just update the flags so the correct value // will be used once the channel is registered flags &= ~Native.EPOLLIN; } } private void modifyEvents() throws IOException { if (isOpen() && isRegistered()) { ((EpollEventLoop) eventLoop()).modify(this); } } @Override protected void doRegister() throws Exception { // Just in case the previous EventLoop was shutdown abruptly, or an event is still pending on the old EventLoop // make sure the epollInReadyRunnablePending variable is reset so we will be able to execute the Runnable on the // new EventLoop. epollInReadyRunnablePending = false; ((EpollEventLoop) eventLoop()).add(this); } @Override protected abstract AbstractEpollUnsafe newUnsafe(); /** * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one. */ protected final ByteBuf newDirectBuffer(ByteBuf buf) { return newDirectBuffer(buf, buf); } /** * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the specified holder. * The caller must ensure that the holder releases the original {@link ByteBuf} when the holder is released by * this method. */ protected final ByteBuf newDirectBuffer(Object holder, ByteBuf buf) { final int readableBytes = buf.readableBytes(); if (readableBytes == 0) { ReferenceCountUtil.release(holder); return Unpooled.EMPTY_BUFFER; } final ByteBufAllocator alloc = alloc(); if (alloc.isDirectBufferPooled()) { return newDirectBuffer0(holder, buf, alloc, readableBytes); } final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer(); if (directBuf == null) { return newDirectBuffer0(holder, buf, alloc, readableBytes); } directBuf.writeBytes(buf, buf.readerIndex(), readableBytes); ReferenceCountUtil.safeRelease(holder); return directBuf; } private static ByteBuf newDirectBuffer0(Object holder, ByteBuf buf, ByteBufAllocator alloc, int capacity) { final ByteBuf directBuf = alloc.directBuffer(capacity); directBuf.writeBytes(buf, buf.readerIndex(), capacity); ReferenceCountUtil.safeRelease(holder); return directBuf; } protected static void checkResolvable(InetSocketAddress addr) { if (addr.isUnresolved()) { throw new UnresolvedAddressException(); } } /** * Read bytes into the given {@link ByteBuf} and return the amount. */ protected final int doReadBytes(ByteBuf byteBuf) throws Exception { int writerIndex = byteBuf.writerIndex(); int localReadAmount; unsafe().recvBufAllocHandle().attemptedBytesRead(byteBuf.writableBytes()); if (byteBuf.hasMemoryAddress()) { localReadAmount = socket.recvAddress(byteBuf.memoryAddress(), writerIndex, byteBuf.capacity()); } else { ByteBuffer buf = byteBuf.internalNioBuffer(writerIndex, byteBuf.writableBytes()); localReadAmount = socket.recv(buf, buf.position(), buf.limit()); } if (localReadAmount > 0) { byteBuf.writerIndex(writerIndex + localReadAmount); } return localReadAmount; } protected final int doWriteBytes(ChannelOutboundBuffer in, ByteBuf buf) throws Exception { if (buf.hasMemoryAddress()) { int localFlushedAmount = socket.sendAddress(buf.memoryAddress(), buf.readerIndex(), buf.writerIndex()); if (localFlushedAmount > 0) { in.removeBytes(localFlushedAmount); return 1; } } else { final ByteBuffer nioBuf = buf.nioBufferCount() == 1 ? buf.internalNioBuffer(buf.readerIndex(), buf.readableBytes()) : buf.nioBuffer(); int localFlushedAmount = socket.send(nioBuf, nioBuf.position(), nioBuf.limit()); if (localFlushedAmount > 0) { nioBuf.position(nioBuf.position() + localFlushedAmount); in.removeBytes(localFlushedAmount); return 1; } } return WRITE_STATUS_SNDBUF_FULL; } /** * Write bytes to the socket, with or without a remote address. * Used for datagram and TCP client fast open writes. */ final long doWriteOrSendBytes(ByteBuf data, InetSocketAddress remoteAddress, boolean fastOpen) throws IOException { assert !(fastOpen && remoteAddress == null) : ""fastOpen requires a remote address""; if (data.hasMemoryAddress()) { long memoryAddress = data.memoryAddress(); if (remoteAddress == null) { return socket.sendAddress(memoryAddress, data.readerIndex(), data.writerIndex()); } return socket.sendToAddress(memoryAddress, data.readerIndex(), data.writerIndex(), remoteAddress.getAddress(), remoteAddress.getPort(), fastOpen); } if (data.nioBufferCount() > 1) { IovArray array = ((EpollEventLoop) eventLoop()).cleanIovArray(); array.add(data, data.readerIndex(), data.readableBytes()); int cnt = array.count(); assert cnt != 0; if (remoteAddress == null) { return socket.writevAddresses(array.memoryAddress(0), cnt); } return socket.sendToAddresses(array.memoryAddress(0), cnt, remoteAddress.getAddress(), remoteAddress.getPort(), fastOpen); } ByteBuffer nioData = data.internalNioBuffer(data.readerIndex(), data.readableBytes()); if (remoteAddress == null) { return socket.send(nioData, nioData.position(), nioData.limit()); } return socket.sendTo(nioData, nioData.position(), nioData.limit(), remoteAddress.getAddress(), remoteAddress.getPort(), fastOpen); } protected abstract class AbstractEpollUnsafe extends AbstractUnsafe { boolean readPending; boolean maybeMoreDataToRead; private EpollRecvByteAllocatorHandle allocHandle; private final Runnable epollInReadyRunnable = new Runnable() { @Override public void run() { epollInReadyRunnablePending = false; epollInReady(); } }; /** * Called once EPOLLIN event is ready to be processed */ abstract void epollInReady(); final void epollInBefore() { maybeMoreDataToRead = false; } final void epollInFinally(ChannelConfig config) { maybeMoreDataToRead = allocHandle.maybeMoreDataToRead(); if (allocHandle.isReceivedRdHup() || (readPending && maybeMoreDataToRead)) { // trigger a read again as there may be something left to read and because of epoll ET we // will not get notified again until we read everything from the socket // // It is possible the last fireChannelRead call could cause the user to call read() again, or if // autoRead is true the call to channelReadComplete would also call read, but maybeMoreDataToRead is set // to false before every read operation to prevent re-entry into epollInReady() we will not read from // the underlying OS again unless the user happens to call read again. executeEpollInReadyRunnable(config); } else if (!readPending && !config.isAutoRead()) { // Check if there is a readPending which was not processed yet. // This could be for two reasons: // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method // // See https://github.com/netty/netty/issues/2254 clearEpollIn(); } } final void executeEpollInReadyRunnable(ChannelConfig config) { if (epollInReadyRunnablePending || !isActive() || shouldBreakEpollInReady(config)) { return; } epollInReadyRunnablePending = true; eventLoop().execute(epollInReadyRunnable); } /** * Called once EPOLLRDHUP event is ready to be processed */ final void epollRdHupReady() { // This must happen before we attempt to read. This will ensure reading continues until an error occurs. recvBufAllocHandle().receivedRdHup(); if (isActive()) { // If it is still active, we need to call epollInReady as otherwise we may miss to // read pending data from the underlying file descriptor. // See https://github.com/netty/netty/issues/3709 epollInReady(); } else { // Just to be safe make sure the input marked as closed. shutdownInput(true); } // Clear the EPOLLRDHUP flag to prevent continuously getting woken up on this event. clearEpollRdHup(); } /** * Clear the {@link Native#EPOLLRDHUP} flag from EPOLL, and close on failure. */ private void clearEpollRdHup() { try { clearFlag(Native.EPOLLRDHUP); } catch (IOException e) { pipeline().fireExceptionCaught(e); close(voidPromise()); } } /** * Shutdown the input side of the channel. */ void shutdownInput(boolean rdHup) { if (!socket.isInputShutdown()) { if (isAllowHalfClosure(config())) { try { socket.shutdown(true, false); } catch (IOException ignored) { // We attempted to shutdown and failed, which means the input has already effectively been // shutdown. fireEventAndClose(ChannelInputShutdownEvent.INSTANCE); return; } catch (NotYetConnectedException ignore) { // We attempted to shutdown and failed, which means the input has already effectively been // shutdown. } clearEpollIn0(); pipeline().fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE); } else { close(voidPromise()); } } else if (!rdHup && !inputClosedSeenErrorOnRead) { inputClosedSeenErrorOnRead = true; pipeline().fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE); } } private void fireEventAndClose(Object evt) { pipeline().fireUserEventTriggered(evt); close(voidPromise()); } @Override public EpollRecvByteAllocatorHandle recvBufAllocHandle() { if (allocHandle == null) { allocHandle = newEpollHandle((RecvByteBufAllocator.ExtendedHandle) super.recvBufAllocHandle()); } return allocHandle; } /** * Create a new {@link EpollRecvByteAllocatorHandle} instance. * @param handle The handle to wrap with EPOLL specific logic. */ EpollRecvByteAllocatorHandle newEpollHandle(RecvByteBufAllocator.ExtendedHandle handle) { return new EpollRecvByteAllocatorHandle(handle); } @Override protected final void flush0() { // Flush immediately only when there's no pending flush. // If there's a pending flush operation, event loop will call forceFlush() later, // and thus there's no need to call it now. if (!isFlagSet(Native.EPOLLOUT)) { super.flush0(); } } /** * Called once a EPOLLOUT event is ready to be processed */ final void epollOutReady() { if (connectPromise != null) { // pending connect which is now complete so handle it. finishConnect(); } else if (!socket.isOutputShutdown()) { // directly call super.flush0() to force a flush now super.flush0(); } } protected final void clearEpollIn0() { assert eventLoop().inEventLoop(); try { readPending = false; clearFlag(Native.EPOLLIN); } catch (IOException e) { // When this happens there is something completely wrong with either the filedescriptor or epoll, // so fire the exception through the pipeline and close the Channel. pipeline().fireExceptionCaught(e); unsafe().close(unsafe().voidPromise()); } } @Override public void connect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { if (!promise.setUncancellable() || !ensureOpen(promise)) { return; } try { if (connectPromise != null) { throw new ConnectionPendingException(); } boolean wasActive = isActive(); if (doConnect(remoteAddress, localAddress)) { fulfillConnectPromise(promise, wasActive); } else { connectPromise = promise; requestedRemoteAddress = remoteAddress; // Schedule connect timeout. int connectTimeoutMillis = config().getConnectTimeoutMillis(); if (connectTimeoutMillis > 0) { connectTimeoutFuture = eventLoop().schedule(new Runnable() { @Override public void run() { ChannelPromise connectPromise = AbstractEpollChannel.this.connectPromise; if (connectPromise != null && !connectPromise.isDone() && connectPromise.tryFailure(new ConnectTimeoutException( ""connection timed out: "" + remoteAddress))) { close(voidPromise()); } } }, connectTimeoutMillis, TimeUnit.MILLISECONDS); } promise.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture [MASK] ) throws Exception { if ( [MASK] .isCancelled()) { if (connectTimeoutFuture != null) { connectTimeoutFuture.cancel(false); } connectPromise = null; close(voidPromise()); } } }); } } catch (Throwable t) { closeIfClosed(); promise.tryFailure(annotateConnectException(t, remoteAddress)); } } private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { if (promise == null) { // Closed via cancellation and the promise has been notified already. return; } active = true; // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel. // We still need to ensure we call fireChannelActive() in this case. boolean active = isActive(); // trySuccess() will return false if a user cancelled the connection attempt. boolean promiseSet = promise.trySuccess(); // Regardless if the connection attempt was cancelled, channelActive() event should be triggered, // because what happened is what happened. if (!wasActive && active) { pipeline().fireChannelActive(); } // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive(). if (!promiseSet) { close(voidPromise()); } } private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) { if (promise == null) { // Closed via cancellation and the promise has been notified already. return; } // Use tryFailure() instead of setFailure() to avoid the race against cancel(). promise.tryFailure(cause); closeIfClosed(); } private void finishConnect() { // Note this method is invoked by the event loop only if the connection attempt was // neither cancelled nor timed out. assert eventLoop().inEventLoop(); boolean connectStillInProgress = false; try { boolean wasActive = isActive(); if (!doFinishConnect()) { connectStillInProgress = true; return; } fulfillConnectPromise(connectPromise, wasActive); } catch (Throwable t) { fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress)); } finally { if (!connectStillInProgress) { // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used // See https://github.com/netty/netty/issues/1770 if (connectTimeoutFuture != null) { connectTimeoutFuture.cancel(false); } connectPromise = null; } } } /** * Finish the connect */ private boolean doFinishConnect() throws Exception { if (socket.finishConnect()) { clearFlag(Native.EPOLLOUT); if (requestedRemoteAddress instanceof InetSocketAddress) { remote = computeRemoteAddr((InetSocketAddress) requestedRemoteAddress, socket.remoteAddress()); } requestedRemoteAddress = null; return true; } setFlag(Native.EPOLLOUT); return false; } } @Override protected void doBind(SocketAddress local) throws Exception { if (local instanceof InetSocketAddress) { checkResolvable((InetSocketAddress) local); } socket.bind(local); this.local = socket.localAddress(); } /** * Connect to the remote peer */ protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { if (localAddress instanceof InetSocketAddress) { checkResolvable((InetSocketAddress) localAddress); } InetSocketAddress remoteSocketAddr = remoteAddress instanceof InetSocketAddress ? (InetSocketAddress) remoteAddress : null; if (remoteSocketAddr != null) { checkResolvable(remoteSocketAddr); } if (remote != null) { // Check if already connected before trying to connect. This is needed as connect(...) will not return -1 // and set errno to EISCONN if a previous connect(...) attempt was setting errno to EINPROGRESS and finished // later. throw new AlreadyConnectedException(); } if (localAddress != null) { socket.bind(localAddress); } boolean connected = doConnect0(remoteAddress); if (connected) { remote = remoteSocketAddr == null ? remoteAddress : computeRemoteAddr(remoteSocketAddr, socket.remoteAddress()); } // We always need to set the localAddress even if not connected yet as the bind already took place. // // See https://github.com/netty/netty/issues/3463 local = socket.localAddress(); return connected; } boolean doConnect0(SocketAddress remote) throws Exception { boolean success = false; try { boolean connected = socket.connect(remote); if (!connected) { setFlag(Native.EPOLLOUT); } success = true; return connected; } finally { if (!success) { doClose(); } } } @Override protected SocketAddress localAddress0() { return local; } @Override protected SocketAddress remoteAddress0() { return remote; } } ","future " "/* ---------------------------------------------------------------------------- * This file was automatically generated by SWIG (http://www.swig.org). * Version 3.0.11 * * Do not make changes to this file unless you know what you are doing--modify * the SWIG interface file instead. * ----------------------------------------------------------------------------- */ package com.badlogic.gdx.physics.bullet.collision; import com.badlogic.gdx.physics.bullet.BulletBase; import com.badlogic.gdx.physics.bullet.linearmath.*; public class btTriangleMeshShapeData extends BulletBase { private long swigCPtr; protected btTriangleMeshShapeData (final String className, long cPtr, boolean [MASK] ) { super(className, cPtr, [MASK] ); swigCPtr = cPtr; } /** Construct a new btTriangleMeshShapeData, normally you should not need this constructor it's intended for low-level * usage. */ public btTriangleMeshShapeData (long cPtr, boolean [MASK] ) { this(""btTriangleMeshShapeData"", cPtr, [MASK] ); construct(); } @Override protected void reset (long cPtr, boolean [MASK] ) { if (!destroyed) destroy(); super.reset(swigCPtr = cPtr, [MASK] ); } public static long getCPtr (btTriangleMeshShapeData obj) { return (obj == null) ? 0 : obj.swigCPtr; } @Override protected void finalize () throws Throwable { if (!destroyed) destroy(); super.finalize(); } @Override protected synchronized void delete () { if (swigCPtr != 0) { if (swigCMemOwn) { swigCMemOwn = false; CollisionJNI.delete_btTriangleMeshShapeData(swigCPtr); } swigCPtr = 0; } super.delete(); } public void setCollisionShapeData (btCollisionShapeData value) { CollisionJNI.btTriangleMeshShapeData_collisionShapeData_set(swigCPtr, this, btCollisionShapeData.getCPtr(value), value); } public btCollisionShapeData getCollisionShapeData () { long cPtr = CollisionJNI.btTriangleMeshShapeData_collisionShapeData_get(swigCPtr, this); return (cPtr == 0) ? null : new btCollisionShapeData(cPtr, false); } public void setMeshInterface (btStridingMeshInterfaceData value) { CollisionJNI.btTriangleMeshShapeData_meshInterface_set(swigCPtr, this, btStridingMeshInterfaceData.getCPtr(value), value); } public btStridingMeshInterfaceData getMeshInterface () { long cPtr = CollisionJNI.btTriangleMeshShapeData_meshInterface_get(swigCPtr, this); return (cPtr == 0) ? null : new btStridingMeshInterfaceData(cPtr, false); } public void setQuantizedFloatBvh (btQuantizedBvhFloatData value) { CollisionJNI.btTriangleMeshShapeData_quantizedFloatBvh_set(swigCPtr, this, btQuantizedBvhFloatData.getCPtr(value), value); } public btQuantizedBvhFloatData getQuantizedFloatBvh () { long cPtr = CollisionJNI.btTriangleMeshShapeData_quantizedFloatBvh_get(swigCPtr, this); return (cPtr == 0) ? null : new btQuantizedBvhFloatData(cPtr, false); } public void setQuantizedDoubleBvh (btQuantizedBvhDoubleData value) { CollisionJNI.btTriangleMeshShapeData_quantizedDoubleBvh_set(swigCPtr, this, btQuantizedBvhDoubleData.getCPtr(value), value); } public btQuantizedBvhDoubleData getQuantizedDoubleBvh () { long cPtr = CollisionJNI.btTriangleMeshShapeData_quantizedDoubleBvh_get(swigCPtr, this); return (cPtr == 0) ? null : new btQuantizedBvhDoubleData(cPtr, false); } public void setTriangleInfoMap (btTriangleInfoMapData value) { CollisionJNI.btTriangleMeshShapeData_triangleInfoMap_set(swigCPtr, this, btTriangleInfoMapData.getCPtr(value), value); } public btTriangleInfoMapData getTriangleInfoMap () { long cPtr = CollisionJNI.btTriangleMeshShapeData_triangleInfoMap_get(swigCPtr, this); return (cPtr == 0) ? null : new btTriangleInfoMapData(cPtr, false); } public void setCollisionMargin (float value) { CollisionJNI.btTriangleMeshShapeData_collisionMargin_set(swigCPtr, this, value); } public float getCollisionMargin () { return CollisionJNI.btTriangleMeshShapeData_collisionMargin_get(swigCPtr, this); } public void setPad3 (String value) { CollisionJNI.btTriangleMeshShapeData_pad3_set(swigCPtr, this, value); } public String getPad3 () { return CollisionJNI.btTriangleMeshShapeData_pad3_get(swigCPtr, this); } public btTriangleMeshShapeData () { this(CollisionJNI.new_btTriangleMeshShapeData(), true); } } ","cMemoryOwn " "/* * Copyright 2016 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.channel.kqueue; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.AddressedEnvelope; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultAddressedEnvelope; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannelConfig; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.InternetProtocolFamily; import io.netty.channel.unix.DatagramSocketAddress; import io.netty.channel.unix.Errors; import io.netty.channel.unix.IovArray; import io.netty.channel.unix.UnixChannelUtil; import io.netty.util.UncheckedBooleanSupplier; import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.StringUtil; import io.netty.util.internal.UnstableApi; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.PortUnreachableException; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.UnresolvedAddressException; import static io.netty.channel.kqueue.BsdSocket.newSocketDgram; @UnstableApi public final class KQueueDatagramChannel extends AbstractKQueueDatagramChannel implements DatagramChannel { private static final String EXPECTED_TYPES = "" (expected: "" + StringUtil.simpleClassName(DatagramPacket.class) + "", "" + StringUtil.simpleClassName(AddressedEnvelope.class) + '<' + StringUtil.simpleClassName(ByteBuf.class) + "", "" + StringUtil.simpleClassName(InetSocketAddress.class) + "">, "" + StringUtil.simpleClassName(ByteBuf.class) + ')'; private volatile boolean connected; private final KQueueDatagramChannelConfig config; public KQueueDatagramChannel() { super(null, newSocketDgram(), false); config = new KQueueDatagramChannelConfig(this); } public KQueueDatagramChannel(InternetProtocolFamily protocol) { super(null, newSocketDgram(protocol), false); config = new KQueueDatagramChannelConfig(this); } public KQueueDatagramChannel(int fd) { this(new BsdSocket(fd), true); } KQueueDatagramChannel(BsdSocket socket, boolean active) { super(null, socket, active); config = new KQueueDatagramChannelConfig(this); } @Override public InetSocketAddress remoteAddress() { return (InetSocketAddress) super.remoteAddress(); } @Override public InetSocketAddress localAddress() { return (InetSocketAddress) super.localAddress(); } @Override @SuppressWarnings(""deprecation"") public boolean isActive() { return socket.isOpen() && (config.getActiveOnOpen() && isRegistered() || active); } @Override public boolean isConnected() { return connected; } @Override public ChannelFuture joinGroup(InetAddress multicastAddress) { return joinGroup(multicastAddress, newPromise()); } @Override public ChannelFuture joinGroup(InetAddress multicastAddress, ChannelPromise promise) { try { NetworkInterface iface = config().getNetworkInterface(); if (iface == null) { iface = NetworkInterface.getByInetAddress(localAddress().getAddress()); } return joinGroup(multicastAddress, iface, null, promise); } catch (SocketException e) { promise.setFailure(e); } return promise; } @Override public ChannelFuture joinGroup( InetSocketAddress multicastAddress, NetworkInterface networkInterface) { return joinGroup(multicastAddress, networkInterface, newPromise()); } @Override public ChannelFuture joinGroup( InetSocketAddress multicastAddress, NetworkInterface networkInterface, ChannelPromise promise) { return joinGroup(multicastAddress.getAddress(), networkInterface, null, promise); } @Override public ChannelFuture joinGroup( InetAddress multicastAddress, NetworkInterface networkInterface, InetAddress source) { return joinGroup(multicastAddress, networkInterface, source, newPromise()); } @Override public ChannelFuture joinGroup( final InetAddress multicastAddress, final NetworkInterface networkInterface, final InetAddress source, final ChannelPromise promise) { ObjectUtil.checkNotNull(multicastAddress, ""multicastAddress""); ObjectUtil.checkNotNull(networkInterface, ""networkInterface""); promise.setFailure(new UnsupportedOperationException(""Multicast not supported"")); return promise; } @Override public ChannelFuture leaveGroup(InetAddress multicastAddress) { return leaveGroup(multicastAddress, newPromise()); } @Override public ChannelFuture leaveGroup(InetAddress multicastAddress, ChannelPromise promise) { try { return leaveGroup( multicastAddress, NetworkInterface.getByInetAddress(localAddress().getAddress()), null, promise); } catch (SocketException e) { promise.setFailure(e); } return promise; } @Override public ChannelFuture leaveGroup( InetSocketAddress multicastAddress, NetworkInterface networkInterface) { return leaveGroup(multicastAddress, networkInterface, newPromise()); } @Override public ChannelFuture leaveGroup( InetSocketAddress multicastAddress, NetworkInterface networkInterface, ChannelPromise promise) { return leaveGroup(multicastAddress.getAddress(), networkInterface, null, promise); } @Override public ChannelFuture leaveGroup( InetAddress multicastAddress, NetworkInterface networkInterface, InetAddress source) { return leaveGroup(multicastAddress, networkInterface, source, newPromise()); } @Override public ChannelFuture leaveGroup( final InetAddress multicastAddress, final NetworkInterface networkInterface, final InetAddress source, final ChannelPromise promise) { ObjectUtil.checkNotNull(multicastAddress, ""multicastAddress""); ObjectUtil.checkNotNull(networkInterface, ""networkInterface""); promise.setFailure(new UnsupportedOperationException(""Multicast not supported"")); return promise; } @Override public ChannelFuture block( InetAddress multicastAddress, NetworkInterface networkInterface, InetAddress sourceToBlock) { return block(multicastAddress, networkInterface, sourceToBlock, newPromise()); } @Override public ChannelFuture block( final InetAddress multicastAddress, final NetworkInterface networkInterface, final InetAddress sourceToBlock, final ChannelPromise promise) { ObjectUtil.checkNotNull(multicastAddress, ""multicastAddress""); ObjectUtil.checkNotNull(sourceToBlock, ""sourceToBlock""); ObjectUtil.checkNotNull(networkInterface, ""networkInterface""); promise.setFailure(new UnsupportedOperationException(""Multicast not supported"")); return promise; } @Override public ChannelFuture block(InetAddress multicastAddress, InetAddress sourceToBlock) { return block(multicastAddress, sourceToBlock, newPromise()); } @Override public ChannelFuture block( InetAddress multicastAddress, InetAddress sourceToBlock, ChannelPromise promise) { try { return block( multicastAddress, NetworkInterface.getByInetAddress(localAddress().getAddress()), sourceToBlock, promise); } catch (Throwable e) { promise.setFailure(e); } return promise; } @Override protected AbstractKQueueUnsafe newUnsafe() { return new KQueueDatagramChannelUnsafe(); } @Override protected void doBind(SocketAddress localAddress) throws Exception { super.doBind(localAddress); active = true; } @Override protected boolean doWriteMessage(Object msg) throws Exception { final ByteBuf data; InetSocketAddress remoteAddress; if (msg instanceof AddressedEnvelope) { @SuppressWarnings(""unchecked"") AddressedEnvelope envelope = (AddressedEnvelope) msg; data = envelope.content(); remoteAddress = envelope.recipient(); } else { data = (ByteBuf) msg; remoteAddress = null; } final int dataLen = data.readableBytes(); if (dataLen == 0) { return true; } final long [MASK] ; if (data.hasMemoryAddress()) { long memoryAddress = data.memoryAddress(); if (remoteAddress == null) { [MASK] = socket.writeAddress(memoryAddress, data.readerIndex(), data.writerIndex()); } else { [MASK] = socket.sendToAddress(memoryAddress, data.readerIndex(), data.writerIndex(), remoteAddress.getAddress(), remoteAddress.getPort()); } } else if (data.nioBufferCount() > 1) { IovArray array = ((KQueueEventLoop) eventLoop()).cleanArray(); array.add(data, data.readerIndex(), data.readableBytes()); int cnt = array.count(); assert cnt != 0; if (remoteAddress == null) { [MASK] = socket.writevAddresses(array.memoryAddress(0), cnt); } else { [MASK] = socket.sendToAddresses(array.memoryAddress(0), cnt, remoteAddress.getAddress(), remoteAddress.getPort()); } } else { ByteBuffer nioData = data.internalNioBuffer(data.readerIndex(), data.readableBytes()); if (remoteAddress == null) { [MASK] = socket.write(nioData, nioData.position(), nioData.limit()); } else { [MASK] = socket.sendTo(nioData, nioData.position(), nioData.limit(), remoteAddress.getAddress(), remoteAddress.getPort()); } } return [MASK] > 0; } private static void checkUnresolved(AddressedEnvelope envelope) { if (envelope.recipient() instanceof InetSocketAddress && (((InetSocketAddress) envelope.recipient()).isUnresolved())) { throw new UnresolvedAddressException(); } } @Override protected Object filterOutboundMessage(Object msg) { if (msg instanceof DatagramPacket) { DatagramPacket packet = (DatagramPacket) msg; checkUnresolved(packet); ByteBuf content = packet.content(); return UnixChannelUtil.isBufferCopyNeededForWrite(content) ? new DatagramPacket(newDirectBuffer(packet, content), packet.recipient()) : msg; } if (msg instanceof ByteBuf) { ByteBuf buf = (ByteBuf) msg; return UnixChannelUtil.isBufferCopyNeededForWrite(buf) ? newDirectBuffer(buf) : buf; } if (msg instanceof AddressedEnvelope) { @SuppressWarnings(""unchecked"") AddressedEnvelope e = (AddressedEnvelope) msg; checkUnresolved(e); if (e.content() instanceof ByteBuf && (e.recipient() == null || e.recipient() instanceof InetSocketAddress)) { ByteBuf content = (ByteBuf) e.content(); return UnixChannelUtil.isBufferCopyNeededForWrite(content) ? new DefaultAddressedEnvelope( newDirectBuffer(e, content), (InetSocketAddress) e.recipient()) : e; } } throw new UnsupportedOperationException( ""unsupported message type: "" + StringUtil.simpleClassName(msg) + EXPECTED_TYPES); } @Override public KQueueDatagramChannelConfig config() { return config; } @Override protected void doDisconnect() throws Exception { socket.disconnect(); connected = active = false; resetCachedAddresses(); } @Override protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { if (super.doConnect(remoteAddress, localAddress)) { connected = true; return true; } return false; } @Override protected void doClose() throws Exception { super.doClose(); connected = false; } final class KQueueDatagramChannelUnsafe extends AbstractKQueueUnsafe { @Override void readReady(KQueueRecvByteAllocatorHandle allocHandle) { assert eventLoop().inEventLoop(); final DatagramChannelConfig config = config(); if (shouldBreakReadReady(config)) { clearReadFilter0(); return; } final ChannelPipeline pipeline = pipeline(); final ByteBufAllocator allocator = config.getAllocator(); allocHandle.reset(config); readReadyBefore(); Throwable exception = null; try { ByteBuf byteBuf = null; try { boolean connected = isConnected(); do { byteBuf = allocHandle.allocate(allocator); allocHandle.attemptedBytesRead(byteBuf.writableBytes()); final DatagramPacket packet; if (connected) { try { allocHandle.lastBytesRead(doReadBytes(byteBuf)); } catch (Errors.NativeIoException e) { // We need to correctly translate connect errors to match NIO behaviour. if (e.expectedErr() == Errors.ERROR_ECONNREFUSED_NEGATIVE) { PortUnreachableException error = new PortUnreachableException(e.getMessage()); error.initCause(e); throw error; } throw e; } if (allocHandle.lastBytesRead() <= 0) { // nothing was read, release the buffer. byteBuf.release(); byteBuf = null; break; } packet = new DatagramPacket(byteBuf, (InetSocketAddress) localAddress(), (InetSocketAddress) remoteAddress()); } else { final DatagramSocketAddress remoteAddress; if (byteBuf.hasMemoryAddress()) { // has a memory address so use optimized call remoteAddress = socket.recvFromAddress(byteBuf.memoryAddress(), byteBuf.writerIndex(), byteBuf.capacity()); } else { ByteBuffer nioData = byteBuf.internalNioBuffer( byteBuf.writerIndex(), byteBuf.writableBytes()); remoteAddress = socket.recvFrom(nioData, nioData.position(), nioData.limit()); } if (remoteAddress == null) { allocHandle.lastBytesRead(-1); byteBuf.release(); byteBuf = null; break; } InetSocketAddress localAddress = remoteAddress.localAddress(); if (localAddress == null) { localAddress = (InetSocketAddress) localAddress(); } allocHandle.lastBytesRead(remoteAddress.receivedAmount()); byteBuf.writerIndex(byteBuf.writerIndex() + allocHandle.lastBytesRead()); packet = new DatagramPacket(byteBuf, localAddress, remoteAddress); } allocHandle.incMessagesRead(1); readPending = false; pipeline.fireChannelRead(packet); byteBuf = null; // We use the TRUE_SUPPLIER as it is also ok to read less then what we did try to read (as long // as we read anything). } while (allocHandle.continueReading(UncheckedBooleanSupplier.TRUE_SUPPLIER)); } catch (Throwable t) { if (byteBuf != null) { byteBuf.release(); } exception = t; } allocHandle.readComplete(); pipeline.fireChannelReadComplete(); if (exception != null) { pipeline.fireExceptionCaught(exception); } } finally { readReadyFinally(config); } } } } ","writtenBytes " "// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skyframe; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.skyframe.ProcessPackageDirectory.ProcessPackageDirectorySkyFunctionException; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.util.Map; import javax.annotation.Nullable; /** * Computes {@link CollectPackagesUnderDirectoryValue} which describes whether the directory is a * package, or would have been a package but for a package loading error, and whether non-excluded * packages (or errors) exist below each of the directory's subdirectories. As a side effect, loads * all of these packages, in order to interleave the disk-bound work of checking for directories and * the CPU-bound work of package loading. */ public class CollectPackagesUnderDirectoryFunction implements SkyFunction { private final BlazeDirectories directories; public CollectPackagesUnderDirectoryFunction(BlazeDirectories directories) { this.directories = directories; } @Override public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException, ProcessPackageDirectorySkyFunctionException { return new MyTraversalFunction(directories) .visitDirectory((RecursivePkgKey) skyKey.argument(), env); } /** The {@link RecursiveDirectoryTraversalFunction} used by our traversal. */ public static class MyTraversalFunction extends RecursiveDirectoryTraversalFunction< MyPackageDirectoryConsumer, CollectPackagesUnderDirectoryValue> { protected MyTraversalFunction(BlazeDirectories directories) { super(directories); } @Override protected MyPackageDirectoryConsumer getInitialConsumer() { return new MyPackageDirectoryConsumer(); } @Override protected SkyKey getSkyKeyForSubdirectory( RepositoryName repository, RootedPath subdirectory, ImmutableSet excludedSubdirectoriesBeneathSubdirectory) { return CollectPackagesUnderDirectoryValue.key( repository, subdirectory, excludedSubdirectoriesBeneathSubdirectory); } @Override protected CollectPackagesUnderDirectoryValue aggregateWithSubdirectorySkyValues( MyPackageDirectoryConsumer consumer, Map subdirectorySkyValues) { // Aggregate the child subdirectory package state. ImmutableMap.Builder builder = ImmutableMap.builder(); for (SkyKey key : subdirectorySkyValues.keySet()) { RecursivePkgKey [MASK] = (RecursivePkgKey) key.argument(); CollectPackagesUnderDirectoryValue collectPackagesValue = (CollectPackagesUnderDirectoryValue) subdirectorySkyValues.get(key); boolean packagesOrErrorsInSubdirectory = collectPackagesValue.isDirectoryPackage() || collectPackagesValue.getErrorMessage() != null || Iterables.contains( collectPackagesValue .getSubdirectoryTransitivelyContainsPackagesOrErrors() .values(), Boolean.TRUE); builder.put( [MASK] .getRootedPath(), packagesOrErrorsInSubdirectory); } ImmutableMap subdirectories = builder.buildOrThrow(); String errorMessage = consumer.getErrorMessage(); if (errorMessage != null) { return CollectPackagesUnderDirectoryValue.ofError(errorMessage, subdirectories); } return CollectPackagesUnderDirectoryValue.ofNoError( consumer.isDirectoryPackage(), subdirectories); } } private static class MyPackageDirectoryConsumer implements RecursiveDirectoryTraversalFunction.PackageDirectoryConsumer { private boolean isDirectoryPackage; @Nullable private String errorMessage; private MyPackageDirectoryConsumer() {} @Override public void notePackage(PathFragment pkgPath) { isDirectoryPackage = true; } @Override public void notePackageError(String noSuchPackageExceptionErrorMessage) { this.errorMessage = noSuchPackageExceptionErrorMessage; } boolean isDirectoryPackage() { return isDirectoryPackage; } @Nullable String getErrorMessage() { return errorMessage; } } } ","recursivePkgKey " "package com.google.inject.internal; import static com.google.inject.internal.Element.Type.MULTIBINDER; import static com.google.inject.internal.Errors.checkConfiguration; import static com.google.inject.internal.Errors.checkNotNull; import static com.google.inject.name.Names.named; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.binder.LinkedBindingBuilder; import com.google.inject.internal.InternalProviderInstanceBindingImpl.InitializationTiming; import com.google.inject.multibindings.MultibinderBinding; import com.google.inject.multibindings.MultibindingsTargetVisitor; import com.google.inject.spi.BindingTargetVisitor; import com.google.inject.spi.Dependency; import com.google.inject.spi.Message; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.util.Types; import java.lang.reflect.Type; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.function.Function; /** * The actual multibinder plays several roles: * *

As a Multibinder, it acts as a factory for LinkedBindingBuilders for each of the set's * elements. Each binding is given an annotation that identifies it as a part of this set. * *

As a Module, it installs the binding to the set itself. As a module, this implements equals() * and hashcode() in order to trick Guice into executing its configure() method only once. That * makes it so that multiple multibinders can be created for the same target collection, but only * one is bound. Since the list of bindings is retrieved from the injector itself (and not the * multibinder), each multibinder has access to all contributions from all multibinders. * *

As a Provider, this constructs the set instances. * *

We use a subclass to hide 'implements Module, Provider' from the public API. */ public final class RealMultibinder implements Module { /** Implementation of newSetBinder. */ public static RealMultibinder newRealSetBinder(Binder binder, Key key) { RealMultibinder result = new RealMultibinder<>(binder, key); binder.install(result); return result; } @SuppressWarnings(""unchecked"") // wrapping a T in a Set safely returns a Set static TypeLiteral> setOf(TypeLiteral elementType) { Type type = Types.setOf(elementType.getType()); return (TypeLiteral>) TypeLiteral.get(type); } @SuppressWarnings(""unchecked"") static TypeLiteral>> collectionOfProvidersOf( TypeLiteral elementType) { Type providerType = Types.providerOf(elementType.getType()); Type type = Types.collectionOf(providerType); return (TypeLiteral>>) TypeLiteral.get(type); } @SuppressWarnings(""unchecked"") static TypeLiteral>> collectionOfJakartaProvidersOf( TypeLiteral elementType) { Type providerType = Types.newParameterizedType(jakarta.inject.Provider.class, elementType.getType()); Type type = Types.collectionOf(providerType); return (TypeLiteral>>) TypeLiteral.get(type); } @SuppressWarnings(""unchecked"") static TypeLiteral> setOfExtendsOf(TypeLiteral elementType) { Type [MASK] = Types.subtypeOf(elementType.getType()); Type setOfExtendsType = Types.setOf( [MASK] ); return (TypeLiteral>) TypeLiteral.get(setOfExtendsType); } private final BindingSelection bindingSelection; private final Binder binder; RealMultibinder(Binder binder, Key key) { this.binder = checkNotNull(binder, ""binder""); this.bindingSelection = new BindingSelection<>(key); } @SuppressWarnings({""unchecked"", ""rawtypes""}) // we use raw Key to link bindings together. @Override public void configure(Binder binder) { checkConfiguration(!bindingSelection.isInitialized(), ""Multibinder was already initialized""); // Bind the setKey to the provider wrapped w/ extension support. binder .bind(bindingSelection.getSetKey()) .toProvider(new RealMultibinderProvider<>(bindingSelection)); binder.bind(bindingSelection.getSetOfExtendsKey()).to(bindingSelection.getSetKey()); binder .bind(bindingSelection.getCollectionOfProvidersKey()) .toProvider(new RealMultibinderCollectionOfProvidersProvider(bindingSelection)); // The collection this exposes is internally an ImmutableList, so it's OK to massage // the guice Provider to jakarta Provider in the value (since the guice Provider implements // jakarta Provider). binder .bind(bindingSelection.getCollectionOfJakartaProvidersKey()) .to((Key) bindingSelection.getCollectionOfProvidersKey()); } public void permitDuplicates() { binder.install(new PermitDuplicatesModule(bindingSelection.getPermitDuplicatesKey())); } /** Adds a new entry to the set and returns the key for it. */ Key getKeyForNewItem() { checkConfiguration(!bindingSelection.isInitialized(), ""Multibinder was already initialized""); return Key.get( bindingSelection.getElementTypeLiteral(), new RealElement(bindingSelection.getSetName(), MULTIBINDER, """")); } public LinkedBindingBuilder addBinding() { return binder.bind(getKeyForNewItem()); } // These methods are used by RealMapBinder Key> getSetKey() { return bindingSelection.getSetKey(); } TypeLiteral getElementTypeLiteral() { return bindingSelection.getElementTypeLiteral(); } String getSetName() { return bindingSelection.getSetName(); } boolean permitsDuplicates(Injector injector) { return bindingSelection.permitsDuplicates(injector); } boolean containsElement(com.google.inject.spi.Element element) { return bindingSelection.containsElement(element); } /** * Base implement of {@link InternalProviderInstanceBindingImpl.Factory} that works based on a * {@link BindingSelection}, allowing provider instances for various bindings to be implemented * with less duplication. */ private abstract static class BaseFactory extends InternalProviderInstanceBindingImpl.Factory { final Function, ImmutableSet>> dependenciesFn; final BindingSelection bindingSelection; BaseFactory( BindingSelection bindingSelection, Function, ImmutableSet>> dependenciesFn) { // While Multibinders only depend on bindings created in modules so we could theoretically // initialize eagerly, they also depend on // 1. findBindingsByType returning results // 2. being able to call BindingImpl.acceptTargetVisitor // neither of those is available during eager initialization, so we use DELAYED super(InitializationTiming.DELAYED); this.bindingSelection = bindingSelection; this.dependenciesFn = dependenciesFn; } @Override void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { bindingSelection.initialize(injector, errors); doInitialize(); } abstract void doInitialize(); @Override public Set> getDependencies() { return dependenciesFn.apply(bindingSelection); } @Override public boolean equals(Object obj) { return getClass().isInstance(obj) && bindingSelection.equals(((BaseFactory) obj).bindingSelection); } @Override public int hashCode() { return bindingSelection.hashCode(); } } /** * Provider instance implementation that provides the actual set of values. This is parameterized * so it can be used to supply a {@code Set} and {@code Set}, the latter being * useful for Kotlin support. */ private static final class RealMultibinderProvider extends BaseFactory> implements ProviderWithExtensionVisitor>, MultibinderBinding> { List> bindings; SingleParameterInjector[] injectors; boolean permitDuplicates; RealMultibinderProvider(BindingSelection bindingSelection) { // Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8 super(bindingSelection, bs -> bs.getDependencies()); } @Override protected void doInitialize() { bindings = bindingSelection.getBindings(); injectors = bindingSelection.getParameterInjectors(); permitDuplicates = bindingSelection.permitsDuplicates(); } @Override protected ImmutableSet doProvision(InternalContext context, Dependency dependency) throws InternalProvisionException { SingleParameterInjector[] localInjectors = injectors; if (localInjectors == null) { // if localInjectors == null, then we have no bindings so return the empty set. return ImmutableSet.of(); } // Ideally we would just add to an ImmutableSet.Builder, but if we did that and there were // duplicates we wouldn't be able to tell which one was the duplicate. So to manage this we // first put everything into an array and then construct the set. This way if something gets // dropped we can figure out what it is. @SuppressWarnings(""unchecked"") T[] values = (T[]) new Object[localInjectors.length]; for (int i = 0; i < localInjectors.length; i++) { SingleParameterInjector parameterInjector = localInjectors[i]; T newValue = parameterInjector.inject(context); if (newValue == null) { throw newNullEntryException(i); } values[i] = newValue; } ImmutableSet set = ImmutableSet.copyOf(values); // There are fewer items in the set than the array. Figure out which one got dropped. if (!permitDuplicates && set.size() < values.length) { throw newDuplicateValuesException(values); } return set; } private InternalProvisionException newNullEntryException(int i) { return InternalProvisionException.create( ErrorId.NULL_ELEMENT_IN_SET, ""Set injection failed due to null element bound at: %s"", bindings.get(i).getSource()); } private InternalProvisionException newDuplicateValuesException(T[] values) { Message message = new Message( GuiceInternal.GUICE_INTERNAL, ErrorId.DUPLICATE_ELEMENT, new DuplicateElementError( bindingSelection.getSetKey(), bindings, values, ImmutableList.of(getSource()))); return new InternalProvisionException(message); } @SuppressWarnings(""unchecked"") @Override public V acceptExtensionVisitor( BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor, V>) visitor).visit(this); } else { return visitor.visit(binding); } } @Override public Key> getSetKey() { return bindingSelection.getSetKey(); } @Override public ImmutableSet> getAlternateSetKeys() { return ImmutableSet.of( (Key) bindingSelection.getCollectionOfProvidersKey(), (Key) bindingSelection.getCollectionOfJakartaProvidersKey(), (Key) bindingSelection.getSetOfExtendsKey()); } @Override public TypeLiteral getElementTypeLiteral() { return bindingSelection.getElementTypeLiteral(); } @Override public List> getElements() { return bindingSelection.getElements(); } @Override public boolean permitsDuplicates() { return bindingSelection.permitsDuplicates(); } @Override public boolean containsElement(com.google.inject.spi.Element element) { return bindingSelection.containsElement(element); } } /** * Implementation of BaseFactory that exposes a collection of providers of the values in the set. */ private static final class RealMultibinderCollectionOfProvidersProvider extends BaseFactory>> { ImmutableList> providers; RealMultibinderCollectionOfProvidersProvider(BindingSelection bindingSelection) { // Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8 super(bindingSelection, bs -> bs.getProviderDependencies()); } @Override protected void doInitialize() { ImmutableList.Builder> providers = ImmutableList.builder(); for (Binding binding : bindingSelection.getBindings()) { providers.add(binding.getProvider()); } this.providers = providers.build(); } @Override protected ImmutableList> doProvision( InternalContext context, Dependency dependency) { return providers; } } private static final class BindingSelection { // prior to initialization we declare just a dependency on the injector, but as soon as we are // initialized we swap to dependencies on the elements. private static final ImmutableSet> MODULE_DEPENDENCIES = ImmutableSet.>of(Dependency.get(Key.get(Injector.class))); private final TypeLiteral elementType; private final Key> setKey; // these are all lazily allocated private String setName; private Key>> collectionOfProvidersKey; private Key>> collectionOfJakartaProvidersKey; private Key> setOfExtendsKey; private Key permitDuplicatesKey; private boolean isInitialized; /* a binding for each element in the set. null until initialization, non-null afterwards */ private ImmutableList> bindings; // Starts out as Injector and gets set up properly after initialization private ImmutableSet> dependencies = MODULE_DEPENDENCIES; private ImmutableSet> providerDependencies = MODULE_DEPENDENCIES; /** whether duplicates are allowed. Possibly configured by a different instance */ private boolean permitDuplicates; private SingleParameterInjector[] parameterinjectors; BindingSelection(Key key) { this.setKey = key.ofType(setOf(key.getTypeLiteral())); this.elementType = key.getTypeLiteral(); } void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { // This will be called multiple times, once by each Factory. We only want // to do the work to initialize everything once, so guard this code with // isInitialized. if (isInitialized) { return; } List> bindings = Lists.newArrayList(); Set index = Sets.newHashSet(); Indexer indexer = new Indexer(injector); List> dependencies = Lists.newArrayList(); List> providerDependencies = Lists.newArrayList(); for (Binding entry : injector.findBindingsByType(elementType)) { if (keyMatches(entry.getKey())) { @SuppressWarnings(""unchecked"") // protected by findBindingsByType() Binding binding = (Binding) entry; if (index.add(binding.acceptTargetVisitor(indexer))) { // TODO(lukes): most of these are linked bindings since user bindings are linked to // a user binding through the @Element annotation. Since this is an implementation // detail we could 'dereference' the @Element if it is a LinkedBinding and avoid // provisioning through the FactoryProxy at runtime. // Ditto for OptionalBinder/MapBinder bindings.add(binding); Key key = binding.getKey(); // TODO(lukes): we should mark this as a non-nullable dependency since we don't accept // null. // Add a dependency on Key dependencies.add(Dependency.get(key)); // and add a dependency on Key> providerDependencies.add( Dependency.get(key.ofType(Types.providerOf(key.getTypeLiteral().getType())))); } } } this.bindings = ImmutableList.copyOf(bindings); this.dependencies = ImmutableSet.copyOf(dependencies); this.providerDependencies = ImmutableSet.copyOf(providerDependencies); this.permitDuplicates = permitsDuplicates(injector); // This is safe because all our dependencies are assignable to T and we never assign to // elements of this array. @SuppressWarnings(""unchecked"") SingleParameterInjector[] typed = (SingleParameterInjector[]) injector.getParametersInjectors(dependencies, errors); this.parameterinjectors = typed; isInitialized = true; } boolean permitsDuplicates(Injector injector) { return injector.getBindings().containsKey(getPermitDuplicatesKey()); } ImmutableList> getBindings() { checkConfiguration(isInitialized, ""not initialized""); return bindings; } SingleParameterInjector[] getParameterInjectors() { checkConfiguration(isInitialized, ""not initialized""); return parameterinjectors; } ImmutableSet> getDependencies() { return dependencies; } ImmutableSet> getProviderDependencies() { return providerDependencies; } String getSetName() { // lazily initialized since most selectors don't survive module installation. if (setName == null) { setName = Annotations.nameOf(setKey); } return setName; } Key getPermitDuplicatesKey() { Key local = permitDuplicatesKey; if (local == null) { local = permitDuplicatesKey = Key.get(Boolean.class, named(toString() + "" permits duplicates"")); } return local; } Key>> getCollectionOfProvidersKey() { Key>> local = collectionOfProvidersKey; if (local == null) { local = collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType)); } return local; } Key>> getCollectionOfJakartaProvidersKey() { Key>> local = collectionOfJakartaProvidersKey; if (local == null) { local = collectionOfJakartaProvidersKey = setKey.ofType(collectionOfJakartaProvidersOf(elementType)); } return local; } Key> getSetOfExtendsKey() { Key> local = setOfExtendsKey; if (local == null) { local = setOfExtendsKey = setKey.ofType(setOfExtendsOf(elementType)); } return local; } boolean isInitialized() { return isInitialized; } // MultibinderBinding API methods TypeLiteral getElementTypeLiteral() { return elementType; } Key> getSetKey() { return setKey; } @SuppressWarnings(""unchecked"") List> getElements() { if (isInitialized()) { return (List>) (List) bindings; // safe because bindings is immutable. } else { throw new UnsupportedOperationException(""getElements() not supported for module bindings""); } } boolean permitsDuplicates() { if (isInitialized()) { return permitDuplicates; } else { throw new UnsupportedOperationException( ""permitsDuplicates() not supported for module bindings""); } } boolean containsElement(com.google.inject.spi.Element element) { if (element instanceof Binding) { Binding binding = (Binding) element; return keyMatches(binding.getKey()) || binding.getKey().equals(getPermitDuplicatesKey()) || binding.getKey().equals(setKey) || binding.getKey().equals(collectionOfProvidersKey) || binding.getKey().equals(collectionOfJakartaProvidersKey) || binding.getKey().equals(setOfExtendsKey); } else { return false; } } private boolean keyMatches(Key key) { return key.getTypeLiteral().equals(elementType) && key.getAnnotation() instanceof Element && ((Element) key.getAnnotation()).setName().equals(getSetName()) && ((Element) key.getAnnotation()).type() == MULTIBINDER; } @Override public boolean equals(Object obj) { if (obj instanceof BindingSelection) { return setKey.equals(((BindingSelection) obj).setKey); } return false; } @Override public int hashCode() { return setKey.hashCode(); } @Override public String toString() { return (getSetName().isEmpty() ? """" : getSetName() + "" "") + ""Multibinder<"" + elementType + "">""; } } @Override public boolean equals(Object o) { return o instanceof RealMultibinder && ((RealMultibinder) o).bindingSelection.equals(bindingSelection); } @Override public int hashCode() { return bindingSelection.hashCode(); } /** * We install the permit duplicates configuration as its own binding, all by itself. This way, if * only one of a multibinder's users remember to call permitDuplicates(), they're still permitted. * *

This is like setting a global variable in the injector so that each instance of the * multibinder will have the same value for permitDuplicates, even if it is only set on one of * them. */ private static class PermitDuplicatesModule extends AbstractModule { private final Key key; PermitDuplicatesModule(Key key) { this.key = key; } @Override protected void configure() { bind(key).toInstance(true); } @Override public boolean equals(Object o) { return o instanceof PermitDuplicatesModule && ((PermitDuplicatesModule) o).key.equals(key); } @Override public int hashCode() { return getClass().hashCode() ^ key.hashCode(); } } } ","extendsType " "/* * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.auto.value.processor; import static com.google.common.truth.Truth.assertThat; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; import static java.util.stream.Collectors.joining; import com.google.auto.common.MoreTypes; import com.google.auto.value.processor.MissingTypes.MissingTypeException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationRule; import com.google.testing.compile.JavaFileObjects; import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ErrorType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for {@link TypeEncoder}. * * @author emcmanus@google.com (Éamonn McManus) */ @RunWith(JUnit4.class) public class TypeEncoderTest { @Rule public final CompilationRule compilationRule = new CompilationRule(); private Types typeUtils; private Elements elementUtils; @Before public void setUp() { typeUtils = compilationRule.getTypes(); elementUtils = compilationRule.getElements(); } /** * Assert that the fake program returned by fakeProgramForTypes has the given list of imports and * the given list of spellings. Here, ""spellings"" means the way each type is referenced in the * decoded program, for example {@code Timer} if {@code java.util.Timer} can be imported, or * {@code java.util.Timer} if not. * *

We construct a fake program that references each of the given types in turn. * TypeEncoder.decode doesn't have any real notion of Java syntax, so our program just consists of * START and END markers around the {@code `import`} tag, followed by each type in braces, as * encoded by TypeEncoder.encode. Once decoded, the program should consist of the appropriate * imports (inside START...END) and each type in braces, spelled appropriately. * * @param fakePackage the package that TypeEncoder should consider the fake program to be in. * Classes in the same package don't usually need to be imported. */ private void assertTypeImportsAndSpellings( Set types, String fakePackage, List imports, List spellings) { String fakeProgram = ""START\n`import`\nEND\n"" + types.stream().map(TypeEncoder::encode).collect(joining(""}\n{"", ""{"", ""}"")); String decoded = TypeEncoder.decode( fakeProgram, elementUtils, typeUtils, fakePackage, baseWithoutContainedTypes()); String [MASK] = ""START\n"" + imports.stream().map(s -> ""import "" + s + "";\n"").collect(joining()) + ""\nEND\n"" + spellings.stream().collect(joining(""}\n{"", ""{"", ""}"")); assertThat(decoded).isEqualTo( [MASK] ); } private static class MultipleBounds & Comparable, V> {} @Test public void testImportsForNoTypes() { assertTypeImportsAndSpellings( typeMirrorSet(), ""foo.bar"", ImmutableList.of(), ImmutableList.of()); } @Test public void testImportsForImplicitlyImportedTypes() { Set types = typeMirrorSet( typeMirrorOf(java.lang.String.class), typeMirrorOf(javax.management.MBeanServer.class), // Same package, so no import. typeUtils.getPrimitiveType(TypeKind.INT), typeUtils.getPrimitiveType(TypeKind.BOOLEAN)); assertTypeImportsAndSpellings( types, ""javax.management"", ImmutableList.of(), ImmutableList.of(""String"", ""MBeanServer"", ""int"", ""boolean"")); } @Test public void testImportsForPlainTypes() { Set types = typeMirrorSet( typeUtils.getPrimitiveType(TypeKind.INT), typeMirrorOf(java.lang.String.class), typeMirrorOf(java.net.Proxy.class), typeMirrorOf(java.net.Proxy.Type.class), typeMirrorOf(java.util.regex.Pattern.class), typeMirrorOf(javax.management.MBeanServer.class)); assertTypeImportsAndSpellings( types, ""foo.bar"", ImmutableList.of( ""java.net.Proxy"", ""java.util.regex.Pattern"", ""javax.management.MBeanServer""), ImmutableList.of(""int"", ""String"", ""Proxy"", ""Proxy.Type"", ""Pattern"", ""MBeanServer"")); } @Test public void testImportsForComplicatedTypes() { TypeElement list = typeElementOf(java.util.List.class); TypeElement map = typeElementOf(java.util.Map.class); Set types = typeMirrorSet( typeUtils.getPrimitiveType(TypeKind.INT), typeMirrorOf(java.util.regex.Pattern.class), typeUtils.getDeclaredType( list, // List typeMirrorOf(java.util.Timer.class)), typeUtils.getDeclaredType( map, // Map typeUtils.getWildcardType(typeMirrorOf(java.util.Timer.class), null), typeUtils.getWildcardType(null, typeMirrorOf(java.math.BigInteger.class)))); // Timer is referenced twice but should obviously only be imported once. assertTypeImportsAndSpellings( types, ""foo.bar"", ImmutableList.of( ""java.math.BigInteger"", ""java.util.List"", ""java.util.Map"", ""java.util.Timer"", ""java.util.regex.Pattern""), ImmutableList.of( ""int"", ""Pattern"", ""List"", ""Map"")); } @Test public void testImportsForArrayTypes() { TypeElement list = typeElementOf(java.util.List.class); TypeElement set = typeElementOf(java.util.Set.class); Set types = typeMirrorSet( typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.INT)), typeUtils.getArrayType(typeMirrorOf(java.util.regex.Pattern.class)), typeUtils.getArrayType( // Set[] typeUtils.getDeclaredType( set, typeUtils.getArrayType(typeMirrorOf(java.util.regex.Matcher.class)))), typeUtils.getDeclaredType( list, // List typeUtils.getArrayType(typeMirrorOf(java.util.Timer.class)))); // Timer is referenced twice but should obviously only be imported once. assertTypeImportsAndSpellings( types, ""foo.bar"", ImmutableList.of( ""java.util.List"", ""java.util.Set"", ""java.util.Timer"", ""java.util.regex.Matcher"", ""java.util.regex.Pattern""), ImmutableList.of(""int[]"", ""Pattern[]"", ""Set[]"", ""List"")); } @Test public void testImportNestedType() { Set types = typeMirrorSet(typeMirrorOf(java.net.Proxy.Type.class)); assertTypeImportsAndSpellings( types, ""foo.bar"", ImmutableList.of(""java.net.Proxy""), ImmutableList.of(""Proxy.Type"")); } @Test public void testImportsForAmbiguousNames() { TypeMirror wildcard = typeUtils.getWildcardType(null, null); Set types = typeMirrorSet( typeUtils.getPrimitiveType(TypeKind.INT), typeMirrorOf(java.awt.List.class), typeMirrorOf(java.lang.String.class), typeUtils.getDeclaredType( // List typeElementOf(java.util.List.class), wildcard), typeUtils.getDeclaredType( // Map typeElementOf(java.util.Map.class), wildcard, wildcard)); assertTypeImportsAndSpellings( types, ""foo.bar"", ImmutableList.of(""java.util.Map""), ImmutableList.of(""int"", ""java.awt.List"", ""String"", ""java.util.List"", ""Map"")); } @Test public void testSimplifyJavaLangString() { Set types = typeMirrorSet(typeMirrorOf(java.lang.String.class)); assertTypeImportsAndSpellings(types, ""foo.bar"", ImmutableList.of(), ImmutableList.of(""String"")); } @Test public void testSimplifyJavaLangThreadState() { Set types = typeMirrorSet(typeMirrorOf(java.lang.Thread.State.class)); assertTypeImportsAndSpellings( types, ""foo.bar"", ImmutableList.of(), ImmutableList.of(""Thread.State"")); } @Test public void testSimplifyJavaLangNamesake() { TypeMirror javaLangType = typeMirrorOf(java.lang.RuntimePermission.class); TypeMirror notJavaLangType = typeMirrorOf(com.google.auto.value.processor.testclasses.RuntimePermission.class); Set types = typeMirrorSet(javaLangType, notJavaLangType); assertTypeImportsAndSpellings( types, ""foo.bar"", ImmutableList.of(), ImmutableList.of(javaLangType.toString(), notJavaLangType.toString())); } @Test public void testSimplifyComplicatedTypes() { // This test constructs a set of types and feeds them to TypeEncoder. Then it verifies that // the resultant rewrites of those types are what we would expect. TypeElement list = typeElementOf(java.util.List.class); TypeElement map = typeElementOf(java.util.Map.class); TypeMirror string = typeMirrorOf(java.lang.String.class); TypeMirror integer = typeMirrorOf(java.lang.Integer.class); TypeMirror pattern = typeMirrorOf(java.util.regex.Pattern.class); TypeMirror timer = typeMirrorOf(java.util.Timer.class); TypeMirror bigInteger = typeMirrorOf(java.math.BigInteger.class); ImmutableMap typeMap = ImmutableMap.builder() .put(typeUtils.getPrimitiveType(TypeKind.INT), ""int"") .put(typeUtils.getArrayType(typeUtils.getPrimitiveType(TypeKind.BYTE)), ""byte[]"") .put(pattern, ""Pattern"") .put(typeUtils.getArrayType(pattern), ""Pattern[]"") .put(typeUtils.getArrayType(typeUtils.getArrayType(pattern)), ""Pattern[][]"") .put(typeUtils.getDeclaredType(list, typeUtils.getWildcardType(null, null)), ""List"") .put(typeUtils.getDeclaredType(list, timer), ""List"") .put(typeUtils.getDeclaredType(map, string, integer), ""Map"") .put( typeUtils.getDeclaredType( map, typeUtils.getWildcardType(timer, null), typeUtils.getWildcardType(null, bigInteger)), ""Map"") .build(); assertTypeImportsAndSpellings( typeMap.keySet(), ""foo.bar"", ImmutableList.of( ""java.math.BigInteger"", ""java.util.List"", ""java.util.Map"", ""java.util.Timer"", ""java.util.regex.Pattern""), ImmutableList.copyOf(typeMap.values())); } @Test public void testSimplifyMultipleBounds() { TypeElement multipleBoundsElement = typeElementOf(MultipleBounds.class); TypeMirror multipleBoundsMirror = multipleBoundsElement.asType(); String text = ""`import`\n""; text += ""{"" + TypeEncoder.encode(multipleBoundsMirror) + ""}""; text += ""{"" + TypeEncoder.typeParametersString(multipleBoundsElement.getTypeParameters()) + ""}""; String myPackage = getClass().getPackage().getName(); String decoded = TypeEncoder.decode(text, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes()); String [MASK] = ""import java.util.List;\n\n"" + ""{TypeEncoderTest.MultipleBounds}"" + ""{ & Comparable, V>}""; assertThat(decoded).isEqualTo( [MASK] ); } @SuppressWarnings(""ClassCanBeStatic"") static class Outer { class InnerWithoutTypeParam {} class Middle { class InnerWithTypeParam {} } } @Test public void testOuterParameterizedInnerNot() { TypeElement outerElement = typeElementOf(Outer.class); DeclaredType doubleMirror = typeMirrorOf(Double.class); DeclaredType outerOfDoubleMirror = typeUtils.getDeclaredType(outerElement, doubleMirror); TypeElement innerWithoutTypeParamElement = typeElementOf(Outer.InnerWithoutTypeParam.class); DeclaredType parameterizedInnerWithoutTypeParam = typeUtils.getDeclaredType(outerOfDoubleMirror, innerWithoutTypeParamElement); String encoded = TypeEncoder.encode(parameterizedInnerWithoutTypeParam); String myPackage = getClass().getPackage().getName(); String decoded = TypeEncoder.decode( encoded, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes()); String [MASK] = ""TypeEncoderTest.Outer.InnerWithoutTypeParam""; assertThat(decoded).isEqualTo( [MASK] ); } @Test public void testOuterParameterizedInnerAlso() { TypeElement outerElement = typeElementOf(Outer.class); DeclaredType doubleMirror = typeMirrorOf(Double.class); DeclaredType outerOfDoubleMirror = typeUtils.getDeclaredType(outerElement, doubleMirror); TypeElement middleElement = typeElementOf(Outer.Middle.class); DeclaredType stringMirror = typeMirrorOf(String.class); DeclaredType middleOfStringMirror = typeUtils.getDeclaredType(outerOfDoubleMirror, middleElement, stringMirror); TypeElement innerWithTypeParamElement = typeElementOf(Outer.Middle.InnerWithTypeParam.class); DeclaredType integerMirror = typeMirrorOf(Integer.class); DeclaredType parameterizedInnerWithTypeParam = typeUtils.getDeclaredType(middleOfStringMirror, innerWithTypeParamElement, integerMirror); String encoded = TypeEncoder.encode(parameterizedInnerWithTypeParam); String myPackage = getClass().getPackage().getName(); String decoded = TypeEncoder.decode( encoded, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes()); String [MASK] = ""TypeEncoderTest.Outer.Middle.InnerWithTypeParam""; assertThat(decoded).isEqualTo( [MASK] ); } private static Set typeMirrorSet(TypeMirror... typeMirrors) { Set set = new TypeMirrorSet(); for (TypeMirror typeMirror : typeMirrors) { assertThat(set.add(typeMirror)).isTrue(); } return set; } private TypeElement typeElementOf(Class c) { return elementUtils.getTypeElement(c.getCanonicalName()); } private DeclaredType typeMirrorOf(Class c) { return MoreTypes.asDeclared(typeElementOf(c).asType()); } /** * Returns a ""base type"" for TypeSimplifier that does not contain any nested types. The point * being that every {@code TypeSimplifier} has a base type that the class being generated is going * to extend, and if that class has nested types they will be in scope, and therefore a possible * source of ambiguity. */ private TypeMirror baseWithoutContainedTypes() { return typeMirrorOf(Object.class); } // This test checks that we correctly throw MissingTypeException if there is an ErrorType anywhere // inside a type we are asked to simplify. There's no way to get an ErrorType from typeUtils or // elementUtils, so we need to fire up the compiler with an erroneous source file and use an // annotation processor to capture the resulting ErrorType. Then we can run tests within that // annotation processor, and propagate any failures out of this test. @Test public void testErrorTypes() { JavaFileObject source = JavaFileObjects.forSourceString( ""ExtendsUndefinedType"", ""class ExtendsUndefinedType extends UndefinedParent {}""); Compilation compilation = javac().withProcessors(new ErrorTestProcessor()).compile(source); assertThat(compilation).failed(); assertThat(compilation).hadErrorContaining(""UndefinedParent""); assertThat(compilation).hadErrorCount(1); } @SupportedAnnotationTypes(""*"") private static class ErrorTestProcessor extends AbstractProcessor { Types typeUtils; Elements elementUtils; @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { typeUtils = processingEnv.getTypeUtils(); elementUtils = processingEnv.getElementUtils(); test(); } return false; } private void test() { TypeElement extendsUndefinedType = elementUtils.getTypeElement(""ExtendsUndefinedType""); ErrorType errorType = (ErrorType) extendsUndefinedType.getSuperclass(); TypeElement list = elementUtils.getTypeElement(""java.util.List""); TypeMirror listOfError = typeUtils.getDeclaredType(list, errorType); TypeMirror queryExtendsError = typeUtils.getWildcardType(errorType, null); TypeMirror listOfQueryExtendsError = typeUtils.getDeclaredType(list, queryExtendsError); TypeMirror querySuperError = typeUtils.getWildcardType(null, errorType); TypeMirror listOfQuerySuperError = typeUtils.getDeclaredType(list, querySuperError); TypeMirror arrayOfError = typeUtils.getArrayType(errorType); testErrorType(errorType); testErrorType(listOfError); testErrorType(listOfQueryExtendsError); testErrorType(listOfQuerySuperError); testErrorType(arrayOfError); } @SuppressWarnings(""MissingFail"") // error message gets converted into assertion failure private void testErrorType(TypeMirror typeWithError) { try { TypeEncoder.encode(typeWithError); processingEnv .getMessager() .printMessage(Diagnostic.Kind.ERROR, ""Expected exception for type: "" + typeWithError); } catch (MissingTypeException [MASK] ) { } } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } } } ","expected " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2; import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO; import static com.google.android.exoplayer2.C.TRACK_TYPE_CAMERA_MOTION; import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; import static com.google.android.exoplayer2.Renderer.MSG_SET_PREFERRED_AUDIO_DEVICE; import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE; import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_EFFECTS; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT_RESOLUTION; import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.castNonNull; import static java.lang.Math.max; import static java.lang.Math.min; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioTrack; import android.media.MediaFormat; import android.media.metrics.LogSessionId; import android.os.Handler; import android.os.Looper; import android.util.Pair; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.Renderer.MessageType; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector; import com.google.android.exoplayer2.analytics.MediaMetricsListener; import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.Effect; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.Size; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeoutException; /** * The default implementation of {@link ExoPlayer}. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated /* package */ final class ExoPlayerImpl extends BasePlayer implements ExoPlayer, ExoPlayer.AudioComponent, ExoPlayer.VideoComponent, ExoPlayer.TextComponent, ExoPlayer.DeviceComponent { static { ExoPlayerLibraryInfo.registerModule(""goog.exo.exoplayer""); } private static final String TAG = ""ExoPlayerImpl""; /** * This empty track selector result can only be used for {@link PlaybackInfo#trackSelectorResult} * when the player does not have any track selection made (such as when player is reset, or when * player seeks to an unprepared period). It will not be used as result of any {@link * TrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId, Timeline)} * operation. */ /* package */ final TrackSelectorResult emptyTrackSelectorResult; /* package */ final Commands permanentAvailableCommands; private final ConditionVariable constructorFinished; private final Context applicationContext; private final Player wrappingPlayer; private final Renderer[] renderers; private final TrackSelector trackSelector; private final HandlerWrapper playbackInfoUpdateHandler; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal internalPlayer; private final ListenerSet listeners; private final CopyOnWriteArraySet audioOffloadListeners; private final Timeline.Period period; private final List mediaSourceHolderSnapshots; private final boolean useLazyPreparation; private final MediaSource.Factory mediaSourceFactory; private final AnalyticsCollector analyticsCollector; private final Looper applicationLooper; private final BandwidthMeter bandwidthMeter; private final long seekBackIncrementMs; private final long seekForwardIncrementMs; private final Clock clock; private final ComponentListener componentListener; private final FrameMetadataListener frameMetadataListener; private final AudioBecomingNoisyManager audioBecomingNoisyManager; private final AudioFocusManager audioFocusManager; @Nullable private final StreamVolumeManager streamVolumeManager; private final WakeLockManager wakeLockManager; private final WifiLockManager wifiLockManager; private final long detachSurfaceTimeoutMs; private @RepeatMode int repeatMode; private boolean shuffleModeEnabled; private int pendingOperationAcks; private @DiscontinuityReason int pendingDiscontinuityReason; private boolean pendingDiscontinuity; private @PlayWhenReadyChangeReason int pendingPlayWhenReadyChangeReason; private boolean foregroundMode; private SeekParameters seekParameters; private ShuffleOrder shuffleOrder; private boolean pauseAtEndOfMediaItems; private Commands availableCommands; private MediaMetadata mediaMetadata; private MediaMetadata playlistMetadata; @Nullable private Format videoFormat; @Nullable private Format audioFormat; @Nullable private AudioTrack keepSessionIdAudioTrack; @Nullable private Object videoOutput; @Nullable private Surface ownedSurface; @Nullable private SurfaceHolder surfaceHolder; @Nullable private SphericalGLSurfaceView sphericalGLSurfaceView; private boolean surfaceHolderSurfaceIsVideoOutput; @Nullable private TextureView textureView; private @C.VideoScalingMode int videoScalingMode; private @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy; private Size surfaceSize; @Nullable private DecoderCounters videoDecoderCounters; @Nullable private DecoderCounters audioDecoderCounters; private int audioSessionId; private AudioAttributes audioAttributes; private float volume; private boolean skipSilenceEnabled; private CueGroup currentCueGroup; @Nullable private VideoFrameMetadataListener videoFrameMetadataListener; @Nullable private CameraMotionListener cameraMotionListener; private boolean throwsWhenUsingWrongThread; private boolean hasNotifiedFullWrongThreadWarning; @Nullable private PriorityTaskManager priorityTaskManager; private boolean isPriorityTaskManagerRegistered; private boolean playerReleased; private DeviceInfo deviceInfo; private VideoSize videoSize; // MediaMetadata built from static (TrackGroup Format) and dynamic (onMetadata(Metadata)) metadata // sources. private MediaMetadata staticAndDynamicMediaMetadata; // Playback information when there is no pending seek/set source operation. private PlaybackInfo playbackInfo; // Playback information when there is a pending seek/set source operation. private int maskingWindowIndex; private int maskingPeriodIndex; private long maskingWindowPositionMs; @SuppressLint(""HandlerLeak"") @SuppressWarnings(""deprecation"") // Control flow for old volume commands public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer) { constructorFinished = new ConditionVariable(); try { Log.i( TAG, ""Init "" + Integer.toHexString(System.identityHashCode(this)) + "" ["" + ExoPlayerLibraryInfo.VERSION_SLASHY + ""] ["" + Util.DEVICE_DEBUG_INFO + ""]""); applicationContext = builder.context.getApplicationContext(); analyticsCollector = builder.analyticsCollectorFunction.apply(builder.clock); priorityTaskManager = builder.priorityTaskManager; audioAttributes = builder.audioAttributes; videoScalingMode = builder.videoScalingMode; videoChangeFrameRateStrategy = builder.videoChangeFrameRateStrategy; skipSilenceEnabled = builder.skipSilenceEnabled; detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; componentListener = new ComponentListener(); frameMetadataListener = new FrameMetadataListener(); Handler eventHandler = new Handler(builder.looper); renderers = builder .renderersFactorySupplier .get() .createRenderers( eventHandler, componentListener, componentListener, componentListener, componentListener); checkState(renderers.length > 0); this.trackSelector = builder.trackSelectorSupplier.get(); this.mediaSourceFactory = builder.mediaSourceFactorySupplier.get(); this.bandwidthMeter = builder.bandwidthMeterSupplier.get(); this.useLazyPreparation = builder.useLazyPreparation; this.seekParameters = builder.seekParameters; this.seekBackIncrementMs = builder.seekBackIncrementMs; this.seekForwardIncrementMs = builder.seekForwardIncrementMs; this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems; this.applicationLooper = builder.looper; this.clock = builder.clock; this.wrappingPlayer = wrappingPlayer == null ? this : wrappingPlayer; listeners = new ListenerSet<>( applicationLooper, clock, (listener, flags) -> listener.onEvents(this.wrappingPlayer, new Events(flags))); audioOffloadListeners = new CopyOnWriteArraySet<>(); mediaSourceHolderSnapshots = new ArrayList<>(); shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); emptyTrackSelectorResult = new TrackSelectorResult( new RendererConfiguration[renderers.length], new ExoTrackSelection[renderers.length], Tracks.EMPTY, /* info= */ null); period = new Timeline.Period(); permanentAvailableCommands = new Commands.Builder() .addAll( COMMAND_PLAY_PAUSE, COMMAND_PREPARE, COMMAND_STOP, COMMAND_SET_SPEED_AND_PITCH, COMMAND_SET_SHUFFLE_MODE, COMMAND_SET_REPEAT_MODE, COMMAND_GET_CURRENT_MEDIA_ITEM, COMMAND_GET_TIMELINE, COMMAND_GET_METADATA, COMMAND_SET_PLAYLIST_METADATA, COMMAND_SET_MEDIA_ITEM, COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_GET_TRACKS, COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_VOLUME, COMMAND_SET_VOLUME, COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, COMMAND_RELEASE) .addIf( COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) .addIf(COMMAND_GET_DEVICE_VOLUME, builder.deviceVolumeControlEnabled) .addIf(COMMAND_SET_DEVICE_VOLUME, builder.deviceVolumeControlEnabled) .addIf(COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, builder.deviceVolumeControlEnabled) .addIf(COMMAND_ADJUST_DEVICE_VOLUME, builder.deviceVolumeControlEnabled) .addIf(COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, builder.deviceVolumeControlEnabled) .build(); availableCommands = new Commands.Builder() .addAll(permanentAvailableCommands) .add(COMMAND_SEEK_TO_DEFAULT_POSITION) .add(COMMAND_SEEK_TO_MEDIA_ITEM) .build(); playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null); playbackInfoUpdateListener = playbackInfoUpdate -> playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); analyticsCollector.setPlayer(this.wrappingPlayer, applicationLooper); PlayerId playerId = Util.SDK_INT < 31 ? new PlayerId() : Api31.registerMediaMetricsListener( applicationContext, /* player= */ this, builder.usePlatformDiagnostics); internalPlayer = new ExoPlayerImplInternal( renderers, trackSelector, emptyTrackSelectorResult, builder.loadControlSupplier.get(), bandwidthMeter, repeatMode, shuffleModeEnabled, analyticsCollector, seekParameters, builder.livePlaybackSpeedControl, builder.releaseTimeoutMs, pauseAtEndOfMediaItems, applicationLooper, clock, playbackInfoUpdateListener, playerId, builder.playbackLooper); volume = 1; repeatMode = Player.REPEAT_MODE_OFF; mediaMetadata = MediaMetadata.EMPTY; playlistMetadata = MediaMetadata.EMPTY; staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; maskingWindowIndex = C.INDEX_UNSET; if (Util.SDK_INT < 21) { audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); } else { audioSessionId = Util.generateAudioSessionIdV21(applicationContext); } currentCueGroup = CueGroup.EMPTY_TIME_ZERO; throwsWhenUsingWrongThread = true; addListener(analyticsCollector); bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector); addAudioOffloadListener(componentListener); if (builder.foregroundModeTimeoutMs > 0) { internalPlayer.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs); } audioBecomingNoisyManager = new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener); audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy); audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener); audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null); if (builder.deviceVolumeControlEnabled) { streamVolumeManager = new StreamVolumeManager(builder.context, eventHandler, componentListener); streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); } else { streamVolumeManager = null; } wakeLockManager = new WakeLockManager(builder.context); wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE); wifiLockManager = new WifiLockManager(builder.context); wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); deviceInfo = createDeviceInfo(streamVolumeManager); videoSize = VideoSize.UNKNOWN; surfaceSize = Size.UNKNOWN; trackSelector.setAudioAttributes(audioAttributes); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener); sendRendererMessage( TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener); } finally { constructorFinished.open(); } } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public AudioComponent getAudioComponent() { verifyApplicationThread(); return this; } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public VideoComponent getVideoComponent() { verifyApplicationThread(); return this; } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public TextComponent getTextComponent() { verifyApplicationThread(); return this; } @CanIgnoreReturnValue @SuppressWarnings(""deprecation"") // Returning deprecated class. @Override @Deprecated public DeviceComponent getDeviceComponent() { verifyApplicationThread(); return this; } @Override public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { verifyApplicationThread(); internalPlayer.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); for (AudioOffloadListener listener : audioOffloadListeners) { listener.onExperimentalOffloadSchedulingEnabledChanged(offloadSchedulingEnabled); } } @Override public boolean experimentalIsSleepingForOffload() { verifyApplicationThread(); return playbackInfo.sleepingForOffload; } @Override public Looper getPlaybackLooper() { // Don't verify application thread. We allow calls to this method from any thread. return internalPlayer.getPlaybackLooper(); } @Override public Looper getApplicationLooper() { // Don't verify application thread. We allow calls to this method from any thread. return applicationLooper; } @Override public Clock getClock() { // Don't verify application thread. We allow calls to this method from any thread. return clock; } @Override public void addAudioOffloadListener(AudioOffloadListener listener) { // Don't verify application thread. We allow calls to this method from any thread. audioOffloadListeners.add(listener); } @Override public void removeAudioOffloadListener(AudioOffloadListener listener) { verifyApplicationThread(); audioOffloadListeners.remove(listener); } @Override public Commands getAvailableCommands() { verifyApplicationThread(); return availableCommands; } @Override public @State int getPlaybackState() { verifyApplicationThread(); return playbackInfo.playbackState; } @Override public @PlaybackSuppressionReason int getPlaybackSuppressionReason() { verifyApplicationThread(); return playbackInfo.playbackSuppressionReason; } @Override @Nullable public ExoPlaybackException getPlayerError() { verifyApplicationThread(); return playbackInfo.playbackError; } @Override public void prepare() { verifyApplicationThread(); boolean playWhenReady = getPlayWhenReady(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); if (playbackInfo.playbackState != Player.STATE_IDLE) { return; } PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackError(null); playbackInfo = playbackInfo.copyWithPlaybackState( playbackInfo.timeline.isEmpty() ? STATE_ENDED : STATE_BUFFERING); // Trigger internal prepare first before updating the playback info and notifying external // listeners to ensure that new operations issued in the listener notifications reach the // player after this prepare. The internal player can't change the playback info immediately // because it uses a callback. pendingOperationAcks++; internalPlayer.prepare(); updatePlaybackInfo( playbackInfo, /* ignored */ TIMELINE_CHANGE_REASON_SOURCE_UPDATE, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override @Deprecated public void prepare(MediaSource mediaSource) { verifyApplicationThread(); setMediaSource(mediaSource); prepare(); } @Override @Deprecated public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { verifyApplicationThread(); setMediaSource(mediaSource, resetPosition); prepare(); } @Override public void setMediaItems(List mediaItems, boolean resetPosition) { verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), resetPosition); } @Override public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), startIndex, startPositionMs); } @Override public void setMediaSource(MediaSource mediaSource) { verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource)); } @Override public void setMediaSource(MediaSource mediaSource, long startPositionMs) { verifyApplicationThread(); setMediaSources( Collections.singletonList(mediaSource), /* startWindowIndex= */ 0, startPositionMs); } @Override public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource), resetPosition); } @Override public void setMediaSources(List mediaSources) { verifyApplicationThread(); setMediaSources(mediaSources, /* resetPosition= */ true); } @Override public void setMediaSources(List mediaSources, boolean resetPosition) { verifyApplicationThread(); setMediaSourcesInternal( mediaSources, /* startWindowIndex= */ C.INDEX_UNSET, /* startPositionMs= */ C.TIME_UNSET, /* resetToDefaultPosition= */ resetPosition); } @Override public void setMediaSources( List mediaSources, int startWindowIndex, long startPositionMs) { verifyApplicationThread(); setMediaSourcesInternal( mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false); } @Override public void addMediaItems(int index, List mediaItems) { verifyApplicationThread(); addMediaSources(index, createMediaSources(mediaItems)); } @Override public void addMediaSource(MediaSource mediaSource) { verifyApplicationThread(); addMediaSources(Collections.singletonList(mediaSource)); } @Override public void addMediaSource(int index, MediaSource mediaSource) { verifyApplicationThread(); addMediaSources(index, Collections.singletonList(mediaSource)); } @Override public void addMediaSources(List mediaSources) { verifyApplicationThread(); addMediaSources(/* index= */ mediaSourceHolderSnapshots.size(), mediaSources); } @Override public void addMediaSources(int index, List mediaSources) { verifyApplicationThread(); checkArgument(index >= 0); index = min(index, mediaSourceHolderSnapshots.size()); if (mediaSourceHolderSnapshots.isEmpty()) { // Handle initial items in a playlist as a set operation to ensure state changes and initial // position are updated correctly. setMediaSources(mediaSources, /* resetPosition= */ maskingWindowIndex == C.INDEX_UNSET); return; } PlaybackInfo newPlaybackInfo = addMediaSourcesInternal(playbackInfo, index, mediaSources); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void removeMediaItems(int fromIndex, int toIndex) { verifyApplicationThread(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex); int playlistSize = mediaSourceHolderSnapshots.size(); toIndex = min(toIndex, playlistSize); if (fromIndex >= playlistSize || fromIndex == toIndex) { // Do nothing. return; } PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(playbackInfo, fromIndex, toIndex); boolean positionDiscontinuity = !newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, positionDiscontinuity, DISCONTINUITY_REASON_REMOVE, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { verifyApplicationThread(); checkArgument(fromIndex >= 0 && fromIndex <= toIndex && newFromIndex >= 0); int playlistSize = mediaSourceHolderSnapshots.size(); toIndex = min(toIndex, playlistSize); newFromIndex = min(newFromIndex, playlistSize - (toIndex - fromIndex)); if (fromIndex >= playlistSize || fromIndex == toIndex || fromIndex == newFromIndex) { // Do nothing. return; } Timeline oldTimeline = getCurrentTimeline(); pendingOperationAcks++; Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex); Timeline newTimeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, newTimeline, getPeriodPositionUsAfterTimelineChanged( oldTimeline, newTimeline, getCurrentWindowIndexInternal(playbackInfo), getContentPositionInternal(playbackInfo))); internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void replaceMediaItems(int fromIndex, int toIndex, List mediaItems) { verifyApplicationThread(); checkArgument(fromIndex >= 0 && toIndex >= fromIndex); int playlistSize = mediaSourceHolderSnapshots.size(); if (fromIndex > playlistSize) { // Do nothing. return; } toIndex = min(toIndex, playlistSize); List mediaSources = createMediaSources(mediaItems); if (mediaSourceHolderSnapshots.isEmpty()) { // Handle initial items in a playlist as a set operation to ensure state changes and initial // position are updated correctly. setMediaSources(mediaSources, /* resetPosition= */ maskingWindowIndex == C.INDEX_UNSET); return; } PlaybackInfo newPlaybackInfo = addMediaSourcesInternal(playbackInfo, toIndex, mediaSources); newPlaybackInfo = removeMediaItemsInternal(newPlaybackInfo, fromIndex, toIndex); boolean positionDiscontinuity = !newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, positionDiscontinuity, DISCONTINUITY_REASON_REMOVE, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { verifyApplicationThread(); checkArgument(shuffleOrder.getLength() == mediaSourceHolderSnapshots.size()); this.shuffleOrder = shuffleOrder; Timeline timeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, timeline, maskWindowPositionMsOrGetPeriodPositionUs( timeline, getCurrentMediaItemIndex(), getCurrentPosition())); pendingOperationAcks++; internalPlayer.setShuffleOrder(shuffleOrder); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { verifyApplicationThread(); if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) { return; } this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; internalPlayer.setPauseAtEndOfWindow(pauseAtEndOfMediaItems); } @Override public boolean getPauseAtEndOfMediaItems() { verifyApplicationThread(); return pauseAtEndOfMediaItems; } @Override public void setPlayWhenReady(boolean playWhenReady) { verifyApplicationThread(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); } @Override public boolean getPlayWhenReady() { verifyApplicationThread(); return playbackInfo.playWhenReady; } @Override public void setRepeatMode(@RepeatMode int repeatMode) { verifyApplicationThread(); if (this.repeatMode != repeatMode) { this.repeatMode = repeatMode; internalPlayer.setRepeatMode(repeatMode); listeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode)); updateAvailableCommands(); listeners.flushEvents(); } } @Override public @RepeatMode int getRepeatMode() { verifyApplicationThread(); return repeatMode; } @Override public void setShuffleModeEnabled(boolean shuffleModeEnabled) { verifyApplicationThread(); if (this.shuffleModeEnabled != shuffleModeEnabled) { this.shuffleModeEnabled = shuffleModeEnabled; internalPlayer.setShuffleModeEnabled(shuffleModeEnabled); listeners.queueEvent( Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled)); updateAvailableCommands(); listeners.flushEvents(); } } @Override public boolean getShuffleModeEnabled() { verifyApplicationThread(); return shuffleModeEnabled; } @Override public boolean isLoading() { verifyApplicationThread(); return playbackInfo.isLoading; } @Override public void seekTo( int mediaItemIndex, long positionMs, @Player.Command int seekCommand, boolean isRepeatingCurrentItem) { verifyApplicationThread(); checkArgument(mediaItemIndex >= 0); analyticsCollector.notifySeekStarted(); Timeline timeline = playbackInfo.timeline; if (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount()) { return; } pendingOperationAcks++; if (isPlayingAd()) { // TODO: Investigate adding support for seeking during ads. This is complicated to do in // general because the midroll ad preceding the seek destination must be played before the // content position can be played, if a different ad is playing at the moment. Log.w(TAG, ""seekTo ignored because an ad is playing""); ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate = new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo); playbackInfoUpdate.incrementPendingOperationAcks(1); playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate); return; } PlaybackInfo newPlaybackInfo = playbackInfo; if (playbackInfo.playbackState == Player.STATE_READY || (playbackInfo.playbackState == Player.STATE_ENDED && !timeline.isEmpty())) { newPlaybackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_BUFFERING); } int oldMaskingMediaItemIndex = getCurrentMediaItemIndex(); newPlaybackInfo = maskTimelineAndPosition( newPlaybackInfo, timeline, maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs)); internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs)); updatePlaybackInfo( newPlaybackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ true, /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), oldMaskingMediaItemIndex, isRepeatingCurrentItem); } @Override public long getSeekBackIncrement() { verifyApplicationThread(); return seekBackIncrementMs; } @Override public long getSeekForwardIncrement() { verifyApplicationThread(); return seekForwardIncrementMs; } @Override public long getMaxSeekToPreviousPosition() { verifyApplicationThread(); return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; } @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { verifyApplicationThread(); if (playbackParameters == null) { playbackParameters = PlaybackParameters.DEFAULT; } if (playbackInfo.playbackParameters.equals(playbackParameters)) { return; } PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters); pendingOperationAcks++; internalPlayer.setPlaybackParameters(playbackParameters); updatePlaybackInfo( newPlaybackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } @Override public PlaybackParameters getPlaybackParameters() { verifyApplicationThread(); return playbackInfo.playbackParameters; } @Override public void setSeekParameters(@Nullable SeekParameters seekParameters) { verifyApplicationThread(); if (seekParameters == null) { seekParameters = SeekParameters.DEFAULT; } if (!this.seekParameters.equals(seekParameters)) { this.seekParameters = seekParameters; internalPlayer.setSeekParameters(seekParameters); } } @Override public SeekParameters getSeekParameters() { verifyApplicationThread(); return seekParameters; } @Override public void setForegroundMode(boolean foregroundMode) { verifyApplicationThread(); if (this.foregroundMode != foregroundMode) { this.foregroundMode = foregroundMode; if (!internalPlayer.setForegroundMode(foregroundMode)) { // One of the renderers timed out releasing its resources. stopInternal( ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_SET_FOREGROUND_MODE), PlaybackException.ERROR_CODE_TIMEOUT)); } } } @Override public void stop() { verifyApplicationThread(); audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); stopInternal(/* error= */ null); currentCueGroup = new CueGroup(ImmutableList.of(), playbackInfo.positionUs); } @Override public void release() { Log.i( TAG, ""Release "" + Integer.toHexString(System.identityHashCode(this)) + "" ["" + ExoPlayerLibraryInfo.VERSION_SLASHY + ""] ["" + Util.DEVICE_DEBUG_INFO + ""] ["" + ExoPlayerLibraryInfo.registeredModules() + ""]""); verifyApplicationThread(); if (Util.SDK_INT < 21 && keepSessionIdAudioTrack != null) { keepSessionIdAudioTrack.release(); keepSessionIdAudioTrack = null; } audioBecomingNoisyManager.setEnabled(false); if (streamVolumeManager != null) { streamVolumeManager.release(); } wakeLockManager.setStayAwake(false); wifiLockManager.setStayAwake(false); audioFocusManager.release(); if (!internalPlayer.release()) { // One of the renderers timed out releasing its resources. listeners.sendEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError( ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_RELEASE), PlaybackException.ERROR_CODE_TIMEOUT))); } listeners.release(); playbackInfoUpdateHandler.removeCallbacksAndMessages(null); bandwidthMeter.removeEventListener(analyticsCollector); if (playbackInfo.sleepingForOffload) { playbackInfo = playbackInfo.copyWithEstimatedPosition(); } playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; playbackInfo.totalBufferedDurationUs = 0; analyticsCollector.release(); trackSelector.release(); removeSurfaceCallbacks(); if (ownedSurface != null) { ownedSurface.release(); ownedSurface = null; } if (isPriorityTaskManagerRegistered) { checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = false; } currentCueGroup = CueGroup.EMPTY_TIME_ZERO; playerReleased = true; } @Override public PlayerMessage createMessage(Target target) { verifyApplicationThread(); return createMessageInternal(target); } @Override public int getCurrentPeriodIndex() { verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingPeriodIndex; } else { return playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid); } } @Override public int getCurrentMediaItemIndex() { verifyApplicationThread(); int currentWindowIndex = getCurrentWindowIndexInternal(playbackInfo); return currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex; } @Override public long getDuration() { verifyApplicationThread(); if (isPlayingAd()) { MediaPeriodId periodId = playbackInfo.periodId; playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); long adDurationUs = period.getAdDurationUs(periodId.adGroupIndex, periodId.adIndexInAdGroup); return Util.usToMs(adDurationUs); } return getContentDuration(); } @Override public long getCurrentPosition() { verifyApplicationThread(); return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); } @Override public long getBufferedPosition() { verifyApplicationThread(); if (isPlayingAd()) { return playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId) ? Util.usToMs(playbackInfo.bufferedPositionUs) : getDuration(); } return getContentBufferedPosition(); } @Override public long getTotalBufferedDuration() { verifyApplicationThread(); return Util.usToMs(playbackInfo.totalBufferedDurationUs); } @Override public boolean isPlayingAd() { verifyApplicationThread(); return playbackInfo.periodId.isAd(); } @Override public int getCurrentAdGroupIndex() { verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; } @Override public int getCurrentAdIndexInAdGroup() { verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; } @Override public long getContentPosition() { verifyApplicationThread(); return getContentPositionInternal(playbackInfo); } @Override public long getContentBufferedPosition() { verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingWindowPositionMs; } if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber != playbackInfo.periodId.windowSequenceNumber) { return playbackInfo.timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } long contentBufferedPositionUs = playbackInfo.bufferedPositionUs; if (playbackInfo.loadingMediaPeriodId.isAd()) { Timeline.Period loadingPeriod = playbackInfo.timeline.getPeriodByUid(playbackInfo.loadingMediaPeriodId.periodUid, period); contentBufferedPositionUs = loadingPeriod.getAdGroupTimeUs(playbackInfo.loadingMediaPeriodId.adGroupIndex); if (contentBufferedPositionUs == C.TIME_END_OF_SOURCE) { contentBufferedPositionUs = loadingPeriod.durationUs; } } return Util.usToMs( periodPositionUsToWindowPositionUs( playbackInfo.timeline, playbackInfo.loadingMediaPeriodId, contentBufferedPositionUs)); } @Override public int getRendererCount() { verifyApplicationThread(); return renderers.length; } @Override public @C.TrackType int getRendererType(int index) { verifyApplicationThread(); return renderers[index].getTrackType(); } @Override public Renderer getRenderer(int index) { verifyApplicationThread(); return renderers[index]; } @Override public TrackSelector getTrackSelector() { verifyApplicationThread(); return trackSelector; } @Override public TrackGroupArray getCurrentTrackGroups() { verifyApplicationThread(); return playbackInfo.trackGroups; } @Override public TrackSelectionArray getCurrentTrackSelections() { verifyApplicationThread(); return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections); } @Override public Tracks getCurrentTracks() { verifyApplicationThread(); return playbackInfo.trackSelectorResult.tracks; } @Override public TrackSelectionParameters getTrackSelectionParameters() { verifyApplicationThread(); return trackSelector.getParameters(); } @Override public void setTrackSelectionParameters(TrackSelectionParameters parameters) { verifyApplicationThread(); if (!trackSelector.isSetParametersSupported() || parameters.equals(trackSelector.getParameters())) { return; } trackSelector.setParameters(parameters); listeners.sendEvent( EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, listener -> listener.onTrackSelectionParametersChanged(parameters)); } @Override public MediaMetadata getMediaMetadata() { verifyApplicationThread(); return mediaMetadata; } @Override public MediaMetadata getPlaylistMetadata() { verifyApplicationThread(); return playlistMetadata; } @Override public void setPlaylistMetadata(MediaMetadata playlistMetadata) { verifyApplicationThread(); checkNotNull(playlistMetadata); if (playlistMetadata.equals(this.playlistMetadata)) { return; } this.playlistMetadata = playlistMetadata; listeners.sendEvent( EVENT_PLAYLIST_METADATA_CHANGED, listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata)); } @Override public Timeline getCurrentTimeline() { verifyApplicationThread(); return playbackInfo.timeline; } @Override public void setVideoEffects(List videoEffects) { verifyApplicationThread(); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_VIDEO_EFFECTS, videoEffects); } @Override public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { verifyApplicationThread(); this.videoScalingMode = videoScalingMode; sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); } @Override public @C.VideoScalingMode int getVideoScalingMode() { verifyApplicationThread(); return videoScalingMode; } @Override public void setVideoChangeFrameRateStrategy( @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { verifyApplicationThread(); if (this.videoChangeFrameRateStrategy == videoChangeFrameRateStrategy) { return; } this.videoChangeFrameRateStrategy = videoChangeFrameRateStrategy; sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); } @Override public @C.VideoChangeFrameRateStrategy int getVideoChangeFrameRateStrategy() { verifyApplicationThread(); return videoChangeFrameRateStrategy; } @Override public VideoSize getVideoSize() { verifyApplicationThread(); return videoSize; } @Override public Size getSurfaceSize() { verifyApplicationThread(); return surfaceSize; } @Override public void clearVideoSurface() { verifyApplicationThread(); removeSurfaceCallbacks(); setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } @Override public void clearVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); if (surface != null && surface == videoOutput) { clearVideoSurface(); } } @Override public void setVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); removeSurfaceCallbacks(); setVideoOutputInternal(surface); int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET; maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder == null) { clearVideoSurface(); } else { removeSurfaceCallbacks(); this.surfaceHolderSurfaceIsVideoOutput = true; this.surfaceHolder = surfaceHolder; surfaceHolder.addCallback(componentListener); Surface surface = surfaceHolder.getSurface(); if (surface != null && surface.isValid()) { setVideoOutputInternal(surface); Rect surfaceSize = surfaceHolder.getSurfaceFrame(); maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height()); } else { setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } } } @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { clearVideoSurface(); } } @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); if (surfaceView instanceof VideoDecoderOutputBufferRenderer) { removeSurfaceCallbacks(); setVideoOutputInternal(surfaceView); setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); } else if (surfaceView instanceof SphericalGLSurfaceView) { removeSurfaceCallbacks(); sphericalGLSurfaceView = (SphericalGLSurfaceView) surfaceView; createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) .setPayload(sphericalGLSurfaceView) .send(); sphericalGLSurfaceView.addVideoSurfaceListener(componentListener); setVideoOutputInternal(sphericalGLSurfaceView.getVideoSurface()); setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); } else { setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } } @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } @Override public void setVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView == null) { clearVideoSurface(); } else { removeSurfaceCallbacks(); this.textureView = textureView; if (textureView.getSurfaceTextureListener() != null) { Log.w(TAG, ""Replacing existing SurfaceTextureListener.""); } textureView.setSurfaceTextureListener(componentListener); @Nullable SurfaceTexture surfaceTexture = textureView.isAvailable() ? textureView.getSurfaceTexture() : null; if (surfaceTexture == null) { setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } else { setSurfaceTextureInternal(surfaceTexture); maybeNotifySurfaceSizeChanged(textureView.getWidth(), textureView.getHeight()); } } } @Override public void clearVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView != null && textureView == this.textureView) { clearVideoSurface(); } } @Override public void setAudioAttributes(AudioAttributes newAudioAttributes, boolean handleAudioFocus) { verifyApplicationThread(); if (playerReleased) { return; } if (!Util.areEqual(this.audioAttributes, newAudioAttributes)) { this.audioAttributes = newAudioAttributes; sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, newAudioAttributes); if (streamVolumeManager != null) { streamVolumeManager.setStreamType( Util.getStreamTypeForAudioUsage(newAudioAttributes.usage)); } // Queue event only and flush after updating playWhenReady in case both events are triggered. listeners.queueEvent( EVENT_AUDIO_ATTRIBUTES_CHANGED, listener -> listener.onAudioAttributesChanged(newAudioAttributes)); } audioFocusManager.setAudioAttributes(handleAudioFocus ? newAudioAttributes : null); trackSelector.setAudioAttributes(newAudioAttributes); boolean playWhenReady = getPlayWhenReady(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); listeners.flushEvents(); } @Override public AudioAttributes getAudioAttributes() { verifyApplicationThread(); return audioAttributes; } @Override public void setAudioSessionId(int audioSessionId) { verifyApplicationThread(); if (this.audioSessionId == audioSessionId) { return; } if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { if (Util.SDK_INT < 21) { audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); } else { audioSessionId = Util.generateAudioSessionIdV21(applicationContext); } } else if (Util.SDK_INT < 21) { // We need to re-initialize keepSessionIdAudioTrack to make sure the session is kept alive for // as long as the player is using it. initializeKeepSessionIdAudioTrack(audioSessionId); } this.audioSessionId = audioSessionId; sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); int finalAudioSessionId = audioSessionId; listeners.sendEvent( EVENT_AUDIO_SESSION_ID, listener -> listener.onAudioSessionIdChanged(finalAudioSessionId)); } @Override public int getAudioSessionId() { verifyApplicationThread(); return audioSessionId; } @Override public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { verifyApplicationThread(); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); } @Override public void clearAuxEffectInfo() { verifyApplicationThread(); setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); } @RequiresApi(23) @Override public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { verifyApplicationThread(); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_PREFERRED_AUDIO_DEVICE, audioDeviceInfo); } @Override public void setVolume(float volume) { verifyApplicationThread(); volume = Util.constrainValue(volume, /* min= */ 0, /* max= */ 1); if (this.volume == volume) { return; } this.volume = volume; sendVolumeToRenderers(); float finalVolume = volume; listeners.sendEvent(EVENT_VOLUME_CHANGED, listener -> listener.onVolumeChanged(finalVolume)); } @Override public float getVolume() { verifyApplicationThread(); return volume; } @Override public boolean getSkipSilenceEnabled() { verifyApplicationThread(); return skipSilenceEnabled; } @Override public void setSkipSilenceEnabled(boolean newSkipSilenceEnabled) { verifyApplicationThread(); if (skipSilenceEnabled == newSkipSilenceEnabled) { return; } skipSilenceEnabled = newSkipSilenceEnabled; sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, newSkipSilenceEnabled); listeners.sendEvent( EVENT_SKIP_SILENCE_ENABLED_CHANGED, listener -> listener.onSkipSilenceEnabledChanged(newSkipSilenceEnabled)); } @Override public AnalyticsCollector getAnalyticsCollector() { verifyApplicationThread(); return analyticsCollector; } @Override public void addAnalyticsListener(AnalyticsListener listener) { // Don't verify application thread. We allow calls to this method from any thread. analyticsCollector.addListener(checkNotNull(listener)); } @Override public void removeAnalyticsListener(AnalyticsListener listener) { verifyApplicationThread(); analyticsCollector.removeListener(checkNotNull(listener)); } @Override public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { verifyApplicationThread(); if (playerReleased) { return; } audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy); } @Override public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { verifyApplicationThread(); if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) { return; } if (isPriorityTaskManagerRegistered) { checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK); } if (priorityTaskManager != null && isLoading()) { priorityTaskManager.add(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = true; } else { isPriorityTaskManagerRegistered = false; } this.priorityTaskManager = priorityTaskManager; } @Override @Nullable public Format getVideoFormat() { verifyApplicationThread(); return videoFormat; } @Override @Nullable public Format getAudioFormat() { verifyApplicationThread(); return audioFormat; } @Override @Nullable public DecoderCounters getVideoDecoderCounters() { verifyApplicationThread(); return videoDecoderCounters; } @Override @Nullable public DecoderCounters getAudioDecoderCounters() { verifyApplicationThread(); return audioDecoderCounters; } @Override public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); videoFrameMetadataListener = listener; createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) .setPayload(listener) .send(); } @Override public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); if (videoFrameMetadataListener != listener) { return; } createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) .setPayload(null) .send(); } @Override public void setCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); cameraMotionListener = listener; createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) .setPayload(listener) .send(); } @Override public void clearCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); if (cameraMotionListener != listener) { return; } createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) .setPayload(null) .send(); } @Override public CueGroup getCurrentCues() { verifyApplicationThread(); return currentCueGroup; } @Override public void addListener(Listener listener) { // Don't verify application thread. We allow calls to this method from any thread. listeners.add(checkNotNull(listener)); } @Override public void removeListener(Listener listener) { verifyApplicationThread(); listeners.remove(checkNotNull(listener)); } @Override public void setWakeMode(@C.WakeMode int wakeMode) { verifyApplicationThread(); switch (wakeMode) { case C.WAKE_MODE_NONE: wakeLockManager.setEnabled(false); wifiLockManager.setEnabled(false); break; case C.WAKE_MODE_LOCAL: wakeLockManager.setEnabled(true); wifiLockManager.setEnabled(false); break; case C.WAKE_MODE_NETWORK: wakeLockManager.setEnabled(true); wifiLockManager.setEnabled(true); break; default: break; } } @Override public DeviceInfo getDeviceInfo() { verifyApplicationThread(); return deviceInfo; } @Override public int getDeviceVolume() { verifyApplicationThread(); if (streamVolumeManager != null) { return streamVolumeManager.getVolume(); } else { return 0; } } @Override public boolean isDeviceMuted() { verifyApplicationThread(); if (streamVolumeManager != null) { return streamVolumeManager.isMuted(); } else { return false; } } /** * @deprecated Use {@link #setDeviceVolume(int, int)} instead. */ @Deprecated @Override public void setDeviceVolume(int volume) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setVolume(volume, C.VOLUME_FLAG_SHOW_UI); } } @Override public void setDeviceVolume(int volume, @C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setVolume(volume, flags); } } /** * @deprecated Use {@link #increaseDeviceVolume(int)} instead. */ @Deprecated @Override public void increaseDeviceVolume() { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.increaseVolume(C.VOLUME_FLAG_SHOW_UI); } } @Override public void increaseDeviceVolume(@C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.increaseVolume(flags); } } /** * @deprecated Use {@link #decreaseDeviceVolume(int)} instead. */ @Deprecated @Override public void decreaseDeviceVolume() { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.decreaseVolume(C.VOLUME_FLAG_SHOW_UI); } } @Override public void decreaseDeviceVolume(@C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.decreaseVolume(flags); } } /** * @deprecated Use {@link #setDeviceMuted(boolean, int)} instead. */ @Deprecated @Override public void setDeviceMuted(boolean muted) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setMuted(muted, C.VOLUME_FLAG_SHOW_UI); } } @Override public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) { verifyApplicationThread(); if (streamVolumeManager != null) { streamVolumeManager.setMuted(muted, flags); } } @Override public boolean isTunnelingEnabled() { verifyApplicationThread(); for (@Nullable RendererConfiguration config : playbackInfo.trackSelectorResult.rendererConfigurations) { if (config != null && config.tunneling) { return true; } } return false; } @SuppressWarnings(""deprecation"") // Calling deprecated methods. /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); if (analyticsCollector instanceof DefaultAnalyticsCollector) { ((DefaultAnalyticsCollector) analyticsCollector) .setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); } } /** * Stops the player. * * @param error An optional {@link ExoPlaybackException} to set. */ private void stopInternal(@Nullable ExoPlaybackException error) { PlaybackInfo playbackInfo = this.playbackInfo.copyWithLoadingMediaPeriodId(this.playbackInfo.periodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; playbackInfo.totalBufferedDurationUs = 0; playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); if (error != null) { playbackInfo = playbackInfo.copyWithPlaybackError(error); } pendingOperationAcks++; internalPlayer.stop(); updatePlaybackInfo( playbackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } private int getCurrentWindowIndexInternal(PlaybackInfo playbackInfo) { if (playbackInfo.timeline.isEmpty()) { return maskingWindowIndex; } return playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period) .windowIndex; } private long getContentPositionInternal(PlaybackInfo playbackInfo) { if (playbackInfo.periodId.isAd()) { playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET ? playbackInfo .timeline .getWindow(getCurrentWindowIndexInternal(playbackInfo), window) .getDefaultPositionMs() : period.getPositionInWindowMs() + Util.usToMs(playbackInfo.requestedContentPositionUs); } return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); } private long getCurrentPositionUsInternal(PlaybackInfo playbackInfo) { if (playbackInfo.timeline.isEmpty()) { return Util.msToUs(maskingWindowPositionMs); } long positionUs = playbackInfo.sleepingForOffload ? playbackInfo.getEstimatedPositionUs() : playbackInfo.positionUs; if (playbackInfo.periodId.isAd()) { return positionUs; } return periodPositionUsToWindowPositionUs( playbackInfo.timeline, playbackInfo.periodId, positionUs); } private List createMediaSources(List mediaItems) { List mediaSources = new ArrayList<>(); for (int i = 0; i < mediaItems.size(); i++) { mediaSources.add(mediaSourceFactory.createMediaSource(mediaItems.get(i))); } return mediaSources; } private void handlePlaybackInfo(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate) { pendingOperationAcks -= playbackInfoUpdate.operationAcks; if (playbackInfoUpdate.positionDiscontinuity) { pendingDiscontinuityReason = playbackInfoUpdate.discontinuityReason; pendingDiscontinuity = true; } if (playbackInfoUpdate.hasPlayWhenReadyChangeReason) { pendingPlayWhenReadyChangeReason = playbackInfoUpdate.playWhenReadyChangeReason; } if (pendingOperationAcks == 0) { Timeline newTimeline = playbackInfoUpdate.playbackInfo.timeline; if (!this.playbackInfo.timeline.isEmpty() && newTimeline.isEmpty()) { // Update the masking variables, which are used when the timeline becomes empty because a // ConcatenatingMediaSource has been cleared. maskingWindowIndex = C.INDEX_UNSET; maskingWindowPositionMs = 0; maskingPeriodIndex = 0; } if (!newTimeline.isEmpty()) { List timelines = ((PlaylistTimeline) newTimeline).getChildTimelines(); checkState(timelines.size() == mediaSourceHolderSnapshots.size()); for (int i = 0; i < timelines.size(); i++) { mediaSourceHolderSnapshots.get(i).timeline = timelines.get(i); } } boolean positionDiscontinuity = false; long discontinuityWindowStartPositionUs = C.TIME_UNSET; if (pendingDiscontinuity) { positionDiscontinuity = !playbackInfoUpdate.playbackInfo.periodId.equals(playbackInfo.periodId) || playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs != playbackInfo.positionUs; if (positionDiscontinuity) { discontinuityWindowStartPositionUs = newTimeline.isEmpty() || playbackInfoUpdate.playbackInfo.periodId.isAd() ? playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs : periodPositionUsToWindowPositionUs( newTimeline, playbackInfoUpdate.playbackInfo.periodId, playbackInfoUpdate.playbackInfo.discontinuityStartPositionUs); } } pendingDiscontinuity = false; updatePlaybackInfo( playbackInfoUpdate.playbackInfo, TIMELINE_CHANGE_REASON_SOURCE_UPDATE, pendingPlayWhenReadyChangeReason, positionDiscontinuity, pendingDiscontinuityReason, discontinuityWindowStartPositionUs, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } } // Calling deprecated listeners. @SuppressWarnings(""deprecation"") private void updatePlaybackInfo( PlaybackInfo playbackInfo, @TimelineChangeReason int timelineChangeReason, @PlayWhenReadyChangeReason int playWhenReadyChangeReason, boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, long discontinuityWindowStartPositionUs, int oldMaskingMediaItemIndex, boolean repeatCurrentMediaItem) { // Assign playback info immediately such that all getters return the right values, but keep // snapshot of previous and new state so that listener invocations are triggered correctly. PlaybackInfo previousPlaybackInfo = this.playbackInfo; PlaybackInfo newPlaybackInfo = playbackInfo; this.playbackInfo = playbackInfo; boolean timelineChanged = !previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline); Pair mediaItemTransitionInfo = evaluateMediaItemTransitionReason( newPlaybackInfo, previousPlaybackInfo, positionDiscontinuity, positionDiscontinuityReason, timelineChanged, repeatCurrentMediaItem); boolean mediaItemTransitioned = mediaItemTransitionInfo.first; int mediaItemTransitionReason = mediaItemTransitionInfo.second; MediaMetadata newMediaMetadata = mediaMetadata; @Nullable MediaItem mediaItem = null; if (mediaItemTransitioned) { if (!newPlaybackInfo.timeline.isEmpty()) { int windowIndex = newPlaybackInfo.timeline.getPeriodByUid(newPlaybackInfo.periodId.periodUid, period) .windowIndex; mediaItem = newPlaybackInfo.timeline.getWindow(windowIndex, window).mediaItem; } staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; } if (mediaItemTransitioned || !previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) { staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata .buildUpon() .populateFromMetadata(newPlaybackInfo.staticMetadata) .build(); newMediaMetadata = buildUpdatedMediaMetadata(); } boolean metadataChanged = !newMediaMetadata.equals(mediaMetadata); mediaMetadata = newMediaMetadata; boolean playWhenReadyChanged = previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady; boolean playbackStateChanged = previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState; if (playbackStateChanged || playWhenReadyChanged) { updateWakeAndWifiLock(); } boolean isLoadingChanged = previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading; if (isLoadingChanged) { updatePriorityTaskManagerForIsLoadingChange(newPlaybackInfo.isLoading); } if (timelineChanged) { listeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason)); } if (positionDiscontinuity) { PositionInfo previousPositionInfo = getPreviousPositionInfo( positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, listener -> { listener.onPositionDiscontinuity(positionDiscontinuityReason); listener.onPositionDiscontinuity( previousPositionInfo, positionInfo, positionDiscontinuityReason); }); } if (mediaItemTransitioned) { @Nullable final MediaItem finalMediaItem = mediaItem; listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason)); } if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) { listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError)); if (newPlaybackInfo.playbackError != null) { listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(newPlaybackInfo.playbackError)); } } if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) { trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info); listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newPlaybackInfo.trackSelectorResult.tracks)); } if (metadataChanged) { final MediaMetadata finalMediaMetadata = mediaMetadata; listeners.queueEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(finalMediaMetadata)); } if (isLoadingChanged) { listeners.queueEvent( Player.EVENT_IS_LOADING_CHANGED, listener -> { listener.onLoadingChanged(newPlaybackInfo.isLoading); listener.onIsLoadingChanged(newPlaybackInfo.isLoading); }); } if (playbackStateChanged || playWhenReadyChanged) { listeners.queueEvent( /* eventFlag= */ C.INDEX_UNSET, listener -> listener.onPlayerStateChanged( newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState)); } if (playbackStateChanged) { listeners.queueEvent( Player.EVENT_PLAYBACK_STATE_CHANGED, listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState)); } if (playWhenReadyChanged) { listeners.queueEvent( Player.EVENT_PLAY_WHEN_READY_CHANGED, listener -> listener.onPlayWhenReadyChanged( newPlaybackInfo.playWhenReady, playWhenReadyChangeReason)); } if (previousPlaybackInfo.playbackSuppressionReason != newPlaybackInfo.playbackSuppressionReason) { listeners.queueEvent( Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, listener -> listener.onPlaybackSuppressionReasonChanged( newPlaybackInfo.playbackSuppressionReason)); } if (previousPlaybackInfo.isPlaying() != newPlaybackInfo.isPlaying()) { listeners.queueEvent( Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(newPlaybackInfo.isPlaying())); } if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) { listeners.queueEvent( Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters)); } updateAvailableCommands(); listeners.flushEvents(); if (previousPlaybackInfo.sleepingForOffload != newPlaybackInfo.sleepingForOffload) { for (AudioOffloadListener listener : audioOffloadListeners) { listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload); } } } private PositionInfo getPreviousPositionInfo( @DiscontinuityReason int positionDiscontinuityReason, PlaybackInfo oldPlaybackInfo, int oldMaskingMediaItemIndex) { @Nullable Object oldWindowUid = null; @Nullable Object oldPeriodUid = null; int oldMediaItemIndex = oldMaskingMediaItemIndex; int [MASK] = C.INDEX_UNSET; @Nullable MediaItem oldMediaItem = null; Timeline.Period oldPeriod = new Timeline.Period(); if (!oldPlaybackInfo.timeline.isEmpty()) { oldPeriodUid = oldPlaybackInfo.periodId.periodUid; oldPlaybackInfo.timeline.getPeriodByUid(oldPeriodUid, oldPeriod); oldMediaItemIndex = oldPeriod.windowIndex; [MASK] = oldPlaybackInfo.timeline.getIndexOfPeriod(oldPeriodUid); oldWindowUid = oldPlaybackInfo.timeline.getWindow(oldMediaItemIndex, window).uid; oldMediaItem = window.mediaItem; } long oldPositionUs; long oldContentPositionUs; if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { if (oldPlaybackInfo.periodId.isAd()) { // The old position is the end of the previous ad. oldPositionUs = oldPeriod.getAdDurationUs( oldPlaybackInfo.periodId.adGroupIndex, oldPlaybackInfo.periodId.adIndexInAdGroup); // The ad cue point is stored in the old requested content position. oldContentPositionUs = getRequestedContentPositionUs(oldPlaybackInfo); } else if (oldPlaybackInfo.periodId.nextAdGroupIndex != C.INDEX_UNSET) { // The old position is the end of a clipped content before an ad group. Use the exact ad // cue point as the transition position. oldPositionUs = getRequestedContentPositionUs(playbackInfo); oldContentPositionUs = oldPositionUs; } else { // The old position is the end of a Timeline period. Use the exact duration. oldPositionUs = oldPeriod.positionInWindowUs + oldPeriod.durationUs; oldContentPositionUs = oldPositionUs; } } else if (oldPlaybackInfo.periodId.isAd()) { oldPositionUs = oldPlaybackInfo.positionUs; oldContentPositionUs = getRequestedContentPositionUs(oldPlaybackInfo); } else { oldPositionUs = oldPeriod.positionInWindowUs + oldPlaybackInfo.positionUs; oldContentPositionUs = oldPositionUs; } return new PositionInfo( oldWindowUid, oldMediaItemIndex, oldMediaItem, oldPeriodUid, [MASK] , Util.usToMs(oldPositionUs), Util.usToMs(oldContentPositionUs), oldPlaybackInfo.periodId.adGroupIndex, oldPlaybackInfo.periodId.adIndexInAdGroup); } private PositionInfo getPositionInfo(long discontinuityWindowStartPositionUs) { @Nullable Object newWindowUid = null; @Nullable Object newPeriodUid = null; int newMediaItemIndex = getCurrentMediaItemIndex(); int newPeriodIndex = C.INDEX_UNSET; @Nullable MediaItem newMediaItem = null; if (!playbackInfo.timeline.isEmpty()) { newPeriodUid = playbackInfo.periodId.periodUid; playbackInfo.timeline.getPeriodByUid(newPeriodUid, period); newPeriodIndex = playbackInfo.timeline.getIndexOfPeriod(newPeriodUid); newWindowUid = playbackInfo.timeline.getWindow(newMediaItemIndex, window).uid; newMediaItem = window.mediaItem; } long positionMs = Util.usToMs(discontinuityWindowStartPositionUs); return new PositionInfo( newWindowUid, newMediaItemIndex, newMediaItem, newPeriodUid, newPeriodIndex, positionMs, /* contentPositionMs= */ playbackInfo.periodId.isAd() ? Util.usToMs(getRequestedContentPositionUs(playbackInfo)) : positionMs, playbackInfo.periodId.adGroupIndex, playbackInfo.periodId.adIndexInAdGroup); } private static long getRequestedContentPositionUs(PlaybackInfo playbackInfo) { Timeline.Window window = new Timeline.Window(); Timeline.Period period = new Timeline.Period(); playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET ? playbackInfo.timeline.getWindow(period.windowIndex, window).getDefaultPositionUs() : period.getPositionInWindowUs() + playbackInfo.requestedContentPositionUs; } private Pair evaluateMediaItemTransitionReason( PlaybackInfo playbackInfo, PlaybackInfo oldPlaybackInfo, boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, boolean timelineChanged, boolean repeatCurrentMediaItem) { Timeline oldTimeline = oldPlaybackInfo.timeline; Timeline newTimeline = playbackInfo.timeline; if (newTimeline.isEmpty() && oldTimeline.isEmpty()) { return new Pair<>(/* isTransitioning */ false, /* mediaItemTransitionReason */ C.INDEX_UNSET); } else if (newTimeline.isEmpty() != oldTimeline.isEmpty()) { return new Pair<>(/* isTransitioning */ true, MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED); } int oldWindowIndex = oldTimeline.getPeriodByUid(oldPlaybackInfo.periodId.periodUid, period).windowIndex; Object oldWindowUid = oldTimeline.getWindow(oldWindowIndex, window).uid; int newWindowIndex = newTimeline.getPeriodByUid(playbackInfo.periodId.periodUid, period).windowIndex; Object newWindowUid = newTimeline.getWindow(newWindowIndex, window).uid; if (!oldWindowUid.equals(newWindowUid)) { @Player.MediaItemTransitionReason int transitionReason; if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) { transitionReason = MEDIA_ITEM_TRANSITION_REASON_AUTO; } else if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK) { transitionReason = MEDIA_ITEM_TRANSITION_REASON_SEEK; } else if (timelineChanged) { transitionReason = MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; } else { // A change in window uid must be justified by one of the reasons above. throw new IllegalStateException(); } return new Pair<>(/* isTransitioning */ true, transitionReason); } else { // Only mark changes within the current item as a transition if we are repeating automatically // or via a seek to next/previous. if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION && oldPlaybackInfo.periodId.windowSequenceNumber < playbackInfo.periodId.windowSequenceNumber) { return new Pair<>(/* isTransitioning */ true, MEDIA_ITEM_TRANSITION_REASON_REPEAT); } if (positionDiscontinuity && positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK && repeatCurrentMediaItem) { return new Pair<>(/* isTransitioning */ true, MEDIA_ITEM_TRANSITION_REASON_SEEK); } } return new Pair<>(/* isTransitioning */ false, /* mediaItemTransitionReason */ C.INDEX_UNSET); } private void updateAvailableCommands() { Commands previousAvailableCommands = availableCommands; availableCommands = Util.getAvailableCommands(wrappingPlayer, permanentAvailableCommands); if (!availableCommands.equals(previousAvailableCommands)) { listeners.queueEvent( Player.EVENT_AVAILABLE_COMMANDS_CHANGED, listener -> listener.onAvailableCommandsChanged(availableCommands)); } } private void setMediaSourcesInternal( List mediaSources, int startWindowIndex, long startPositionMs, boolean resetToDefaultPosition) { int currentWindowIndex = getCurrentWindowIndexInternal(playbackInfo); long currentPositionMs = getCurrentPosition(); pendingOperationAcks++; if (!mediaSourceHolderSnapshots.isEmpty()) { removeMediaSourceHolders( /* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size()); } List holders = addMediaSourceHolders(/* index= */ 0, mediaSources); Timeline timeline = createMaskingTimeline(); if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) { throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs); } // Evaluate the actual start position. if (resetToDefaultPosition) { startWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); startPositionMs = C.TIME_UNSET; } else if (startWindowIndex == C.INDEX_UNSET) { startWindowIndex = currentWindowIndex; startPositionMs = currentPositionMs; } PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, timeline, maskWindowPositionMsOrGetPeriodPositionUs(timeline, startWindowIndex, startPositionMs)); // Mask the playback state. int maskingPlaybackState = newPlaybackInfo.playbackState; if (startWindowIndex != C.INDEX_UNSET && newPlaybackInfo.playbackState != STATE_IDLE) { // Position reset to startWindowIndex (results in pending initial seek). if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) { // Setting an empty timeline or invalid seek transitions to ended. maskingPlaybackState = STATE_ENDED; } else { maskingPlaybackState = STATE_BUFFERING; } } newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState); internalPlayer.setMediaSources( holders, startWindowIndex, Util.msToUs(startPositionMs), shuffleOrder); boolean positionDiscontinuity = !playbackInfo.periodId.periodUid.equals(newPlaybackInfo.periodId.periodUid) && !playbackInfo.timeline.isEmpty(); updatePlaybackInfo( newPlaybackInfo, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* positionDiscontinuity= */ positionDiscontinuity, DISCONTINUITY_REASON_REMOVE, /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo), /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } private List addMediaSourceHolders( int index, List mediaSources) { List holders = new ArrayList<>(); for (int i = 0; i < mediaSources.size(); i++) { MediaSourceList.MediaSourceHolder holder = new MediaSourceList.MediaSourceHolder(mediaSources.get(i), useLazyPreparation); holders.add(holder); mediaSourceHolderSnapshots.add( i + index, new MediaSourceHolderSnapshot(holder.uid, holder.mediaSource.getTimeline())); } shuffleOrder = shuffleOrder.cloneAndInsert( /* insertionIndex= */ index, /* insertionCount= */ holders.size()); return holders; } private PlaybackInfo addMediaSourcesInternal( PlaybackInfo playbackInfo, int index, List mediaSources) { Timeline oldTimeline = playbackInfo.timeline; pendingOperationAcks++; List holders = addMediaSourceHolders(index, mediaSources); Timeline newTimeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, newTimeline, getPeriodPositionUsAfterTimelineChanged( oldTimeline, newTimeline, getCurrentWindowIndexInternal(playbackInfo), getContentPositionInternal(playbackInfo))); internalPlayer.addMediaSources(index, holders, shuffleOrder); return newPlaybackInfo; } private PlaybackInfo removeMediaItemsInternal( PlaybackInfo playbackInfo, int fromIndex, int toIndex) { int currentIndex = getCurrentWindowIndexInternal(playbackInfo); long contentPositionMs = getContentPositionInternal(playbackInfo); Timeline oldTimeline = playbackInfo.timeline; int currentMediaSourceCount = mediaSourceHolderSnapshots.size(); pendingOperationAcks++; removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex); Timeline newTimeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, newTimeline, getPeriodPositionUsAfterTimelineChanged( oldTimeline, newTimeline, currentIndex, contentPositionMs)); // Player transitions to STATE_ENDED if the current index is part of the removed tail. final boolean transitionsToEnded = newPlaybackInfo.playbackState != STATE_IDLE && newPlaybackInfo.playbackState != STATE_ENDED && fromIndex < toIndex && toIndex == currentMediaSourceCount && currentIndex >= newPlaybackInfo.timeline.getWindowCount(); if (transitionsToEnded) { newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(STATE_ENDED); } internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder); return newPlaybackInfo; } private void removeMediaSourceHolders(int fromIndex, int toIndexExclusive) { for (int i = toIndexExclusive - 1; i >= fromIndex; i--) { mediaSourceHolderSnapshots.remove(i); } shuffleOrder = shuffleOrder.cloneAndRemove(fromIndex, toIndexExclusive); } private Timeline createMaskingTimeline() { return new PlaylistTimeline(mediaSourceHolderSnapshots, shuffleOrder); } private PlaybackInfo maskTimelineAndPosition( PlaybackInfo playbackInfo, Timeline timeline, @Nullable Pair periodPositionUs) { checkArgument(timeline.isEmpty() || periodPositionUs != null); // Get the old timeline and position before updating playbackInfo. Timeline oldTimeline = playbackInfo.timeline; long oldContentPositionMs = getContentPositionInternal(playbackInfo); // Mask the timeline. playbackInfo = playbackInfo.copyWithTimeline(timeline); if (timeline.isEmpty()) { // Reset periodId and loadingPeriodId. MediaPeriodId dummyMediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline(); long positionUs = Util.msToUs(maskingWindowPositionMs); playbackInfo = playbackInfo.copyWithNewPosition( dummyMediaPeriodId, positionUs, /* requestedContentPositionUs= */ positionUs, /* discontinuityStartPositionUs= */ positionUs, /* totalBufferedDurationUs= */ 0, TrackGroupArray.EMPTY, emptyTrackSelectorResult, /* staticMetadata= */ ImmutableList.of()); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(dummyMediaPeriodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; return playbackInfo; } Object oldPeriodUid = playbackInfo.periodId.periodUid; boolean playingPeriodChanged = !oldPeriodUid.equals(castNonNull(periodPositionUs).first); MediaPeriodId newPeriodId = playingPeriodChanged ? new MediaPeriodId(periodPositionUs.first) : playbackInfo.periodId; long newContentPositionUs = periodPositionUs.second; long oldContentPositionUs = Util.msToUs(oldContentPositionMs); if (!oldTimeline.isEmpty()) { oldContentPositionUs -= oldTimeline.getPeriodByUid(oldPeriodUid, period).getPositionInWindowUs(); } if (playingPeriodChanged || newContentPositionUs < oldContentPositionUs) { checkState(!newPeriodId.isAd()); // The playing period changes or a backwards seek within the playing period occurs. playbackInfo = playbackInfo.copyWithNewPosition( newPeriodId, /* positionUs= */ newContentPositionUs, /* requestedContentPositionUs= */ newContentPositionUs, /* discontinuityStartPositionUs= */ newContentPositionUs, /* totalBufferedDurationUs= */ 0, playingPeriodChanged ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, playingPeriodChanged ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, playingPeriodChanged ? ImmutableList.of() : playbackInfo.staticMetadata); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId); playbackInfo.bufferedPositionUs = newContentPositionUs; } else if (newContentPositionUs == oldContentPositionUs) { // Period position remains unchanged. int loadingPeriodIndex = timeline.getIndexOfPeriod(playbackInfo.loadingMediaPeriodId.periodUid); if (loadingPeriodIndex == C.INDEX_UNSET || timeline.getPeriod(loadingPeriodIndex, period).windowIndex != timeline.getPeriodByUid(newPeriodId.periodUid, period).windowIndex) { // Discard periods after the playing period, if the loading period is discarded or the // playing and loading period are not in the same window. timeline.getPeriodByUid(newPeriodId.periodUid, period); long maskedBufferedPositionUs = newPeriodId.isAd() ? period.getAdDurationUs(newPeriodId.adGroupIndex, newPeriodId.adIndexInAdGroup) : period.durationUs; playbackInfo = playbackInfo.copyWithNewPosition( newPeriodId, /* positionUs= */ playbackInfo.positionUs, /* requestedContentPositionUs= */ playbackInfo.positionUs, playbackInfo.discontinuityStartPositionUs, /* totalBufferedDurationUs= */ maskedBufferedPositionUs - playbackInfo.positionUs, playbackInfo.trackGroups, playbackInfo.trackSelectorResult, playbackInfo.staticMetadata); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId); playbackInfo.bufferedPositionUs = maskedBufferedPositionUs; } } else { checkState(!newPeriodId.isAd()); // A forward seek within the playing period (timeline did not change). long maskedTotalBufferedDurationUs = max( 0, playbackInfo.totalBufferedDurationUs - (newContentPositionUs - oldContentPositionUs)); long maskedBufferedPositionUs = playbackInfo.bufferedPositionUs; if (playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId)) { maskedBufferedPositionUs = newContentPositionUs + maskedTotalBufferedDurationUs; } playbackInfo = playbackInfo.copyWithNewPosition( newPeriodId, /* positionUs= */ newContentPositionUs, /* requestedContentPositionUs= */ newContentPositionUs, /* discontinuityStartPositionUs= */ newContentPositionUs, maskedTotalBufferedDurationUs, playbackInfo.trackGroups, playbackInfo.trackSelectorResult, playbackInfo.staticMetadata); playbackInfo.bufferedPositionUs = maskedBufferedPositionUs; } return playbackInfo; } @Nullable private Pair getPeriodPositionUsAfterTimelineChanged( Timeline oldTimeline, Timeline newTimeline, int currentWindowIndexInternal, long contentPositionMs) { if (oldTimeline.isEmpty() || newTimeline.isEmpty()) { boolean isCleared = !oldTimeline.isEmpty() && newTimeline.isEmpty(); return maskWindowPositionMsOrGetPeriodPositionUs( newTimeline, isCleared ? C.INDEX_UNSET : currentWindowIndexInternal, isCleared ? C.TIME_UNSET : contentPositionMs); } @Nullable Pair oldPeriodPositionUs = oldTimeline.getPeriodPositionUs( window, period, currentWindowIndexInternal, Util.msToUs(contentPositionMs)); Object periodUid = castNonNull(oldPeriodPositionUs).first; if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) { // The old period position is still available in the new timeline. return oldPeriodPositionUs; } // Period uid not found in new timeline. Try to get subsequent period. @Nullable Object nextPeriodUid = ExoPlayerImplInternal.resolveSubsequentPeriod( window, period, repeatMode, shuffleModeEnabled, periodUid, oldTimeline, newTimeline); if (nextPeriodUid != null) { // Reset position to the default position of the window of the subsequent period. newTimeline.getPeriodByUid(nextPeriodUid, period); return maskWindowPositionMsOrGetPeriodPositionUs( newTimeline, period.windowIndex, newTimeline.getWindow(period.windowIndex, window).getDefaultPositionMs()); } else { // No subsequent period found and the new timeline is not empty. Use the default position. return maskWindowPositionMsOrGetPeriodPositionUs( newTimeline, /* windowIndex= */ C.INDEX_UNSET, /* windowPositionMs= */ C.TIME_UNSET); } } @Nullable private Pair maskWindowPositionMsOrGetPeriodPositionUs( Timeline timeline, int windowIndex, long windowPositionMs) { if (timeline.isEmpty()) { // If empty we store the initial seek in the masking variables. maskingWindowIndex = windowIndex; maskingWindowPositionMs = windowPositionMs == C.TIME_UNSET ? 0 : windowPositionMs; maskingPeriodIndex = 0; return null; } if (windowIndex == C.INDEX_UNSET || windowIndex >= timeline.getWindowCount()) { // Use default position of timeline if window index still unset or if a previous initial seek // now turns out to be invalid. windowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); windowPositionMs = timeline.getWindow(windowIndex, window).getDefaultPositionMs(); } return timeline.getPeriodPositionUs(window, period, windowIndex, Util.msToUs(windowPositionMs)); } private long periodPositionUsToWindowPositionUs( Timeline timeline, MediaPeriodId periodId, long positionUs) { timeline.getPeriodByUid(periodId.periodUid, period); positionUs += period.getPositionInWindowUs(); return positionUs; } private PlayerMessage createMessageInternal(Target target) { int currentWindowIndex = getCurrentWindowIndexInternal(playbackInfo); return new PlayerMessage( internalPlayer, target, playbackInfo.timeline, currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex, clock, internalPlayer.getPlaybackLooper()); } /** * Builds a {@link MediaMetadata} from the main sources. * *

{@link MediaItem} {@link MediaMetadata} is prioritized, with any gaps/missing fields * populated by metadata from static ({@link TrackGroup} {@link Format}) and dynamic ({@link * MetadataOutput#onMetadata(Metadata)}) sources. */ private MediaMetadata buildUpdatedMediaMetadata() { Timeline timeline = getCurrentTimeline(); if (timeline.isEmpty()) { return staticAndDynamicMediaMetadata; } MediaItem mediaItem = timeline.getWindow(getCurrentMediaItemIndex(), window).mediaItem; // MediaItem metadata is prioritized over metadata within the media. return staticAndDynamicMediaMetadata.buildUpon().populate(mediaItem.mediaMetadata).build(); } private void removeSurfaceCallbacks() { if (sphericalGLSurfaceView != null) { createMessageInternal(frameMetadataListener) .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) .setPayload(null) .send(); sphericalGLSurfaceView.removeVideoSurfaceListener(componentListener); sphericalGLSurfaceView = null; } if (textureView != null) { if (textureView.getSurfaceTextureListener() != componentListener) { Log.w(TAG, ""SurfaceTextureListener already unset or replaced.""); } else { textureView.setSurfaceTextureListener(null); } textureView = null; } if (surfaceHolder != null) { surfaceHolder.removeCallback(componentListener); surfaceHolder = null; } } private void setSurfaceTextureInternal(SurfaceTexture surfaceTexture) { Surface surface = new Surface(surfaceTexture); setVideoOutputInternal(surface); ownedSurface = surface; } private void setVideoOutputInternal(@Nullable Object videoOutput) { // Note: We don't turn this method into a no-op if the output is being replaced with itself so // as to ensure onRenderedFirstFrame callbacks are still called in this case. List messages = new ArrayList<>(); for (Renderer renderer : renderers) { if (renderer.getTrackType() == TRACK_TYPE_VIDEO) { messages.add( createMessageInternal(renderer) .setType(MSG_SET_VIDEO_OUTPUT) .setPayload(videoOutput) .send()); } } boolean messageDeliveryTimedOut = false; if (this.videoOutput != null && this.videoOutput != videoOutput) { // We're replacing an output. Block to ensure that this output will not be accessed by the // renderers after this method returns. try { for (PlayerMessage message : messages) { message.blockUntilDelivered(detachSurfaceTimeoutMs); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (TimeoutException e) { messageDeliveryTimedOut = true; } if (this.videoOutput == ownedSurface) { // We're replacing a surface that we are responsible for releasing. ownedSurface.release(); ownedSurface = null; } } this.videoOutput = videoOutput; if (messageDeliveryTimedOut) { stopInternal( ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE), PlaybackException.ERROR_CODE_TIMEOUT)); } } /** * Sets the holder of the surface that will be displayed to the user, but which should * not be the output for video renderers. This case occurs when video frames need to be * rendered to an intermediate surface (which is not the one held by the provided holder). * * @param nonVideoOutputSurfaceHolder The holder of the surface that will eventually be displayed * to the user. */ private void setNonVideoOutputSurfaceHolderInternal(SurfaceHolder nonVideoOutputSurfaceHolder) { // Although we won't use the view's surface directly as the video output, still use the holder // to query the surface size, to be informed in changes to the size via componentListener, and // for equality checking in clearVideoSurfaceHolder. surfaceHolderSurfaceIsVideoOutput = false; surfaceHolder = nonVideoOutputSurfaceHolder; surfaceHolder.addCallback(componentListener); Surface surface = surfaceHolder.getSurface(); if (surface != null && surface.isValid()) { Rect surfaceSize = surfaceHolder.getSurfaceFrame(); maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height()); } else { maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } } private void maybeNotifySurfaceSizeChanged(int width, int height) { if (width != surfaceSize.getWidth() || height != surfaceSize.getHeight()) { surfaceSize = new Size(width, height); listeners.sendEvent( EVENT_SURFACE_SIZE_CHANGED, listener -> listener.onSurfaceSizeChanged(width, height)); sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_VIDEO_OUTPUT_RESOLUTION, new Size(width, height)); } } private void sendVolumeToRenderers() { float scaledVolume = volume * audioFocusManager.getVolumeMultiplier(); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_VOLUME, scaledVolume); } private void updatePlayWhenReady( boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand, @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) { playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY; @PlaybackSuppressionReason int playbackSuppressionReason = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY ? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS : Player.PLAYBACK_SUPPRESSION_REASON_NONE; if (playbackInfo.playWhenReady == playWhenReady && playbackInfo.playbackSuppressionReason == playbackSuppressionReason) { return; } pendingOperationAcks++; // Position estimation and copy must occur before changing/masking playback state. PlaybackInfo playbackInfo = this.playbackInfo.sleepingForOffload ? this.playbackInfo.copyWithEstimatedPosition() : this.playbackInfo; playbackInfo = playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason); internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason); updatePlaybackInfo( playbackInfo, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, playWhenReadyChangeReason, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ C.TIME_UNSET, /* ignored */ C.INDEX_UNSET, /* repeatCurrentMediaItem= */ false); } private void updateWakeAndWifiLock() { @State int playbackState = getPlaybackState(); switch (playbackState) { case Player.STATE_READY: case Player.STATE_BUFFERING: boolean isSleeping = experimentalIsSleepingForOffload(); wakeLockManager.setStayAwake(getPlayWhenReady() && !isSleeping); // The wifi lock is not released while sleeping to avoid interrupting downloads. wifiLockManager.setStayAwake(getPlayWhenReady()); break; case Player.STATE_ENDED: case Player.STATE_IDLE: wakeLockManager.setStayAwake(false); wifiLockManager.setStayAwake(false); break; default: throw new IllegalStateException(); } } private void verifyApplicationThread() { // The constructor may be executed on a background thread. Wait with accessing the player from // the app thread until the constructor finished executing. constructorFinished.blockUninterruptible(); if (Thread.currentThread() != getApplicationLooper().getThread()) { String message = Util.formatInvariant( ""Player is accessed on the wrong thread.\n"" + ""Current thread: '%s'\n"" + ""Expected thread: '%s'\n"" + ""See https://developer.android.com/guide/topics/media/issues/"" + ""player-accessed-on-wrong-thread"", Thread.currentThread().getName(), getApplicationLooper().getThread().getName()); if (throwsWhenUsingWrongThread) { throw new IllegalStateException(message); } Log.w(TAG, message, hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException()); hasNotifiedFullWrongThreadWarning = true; } } private void sendRendererMessage( @C.TrackType int trackType, int messageType, @Nullable Object payload) { for (Renderer renderer : renderers) { if (renderer.getTrackType() == trackType) { createMessageInternal(renderer).setType(messageType).setPayload(payload).send(); } } } /** * Initializes {@link #keepSessionIdAudioTrack} to keep an audio session ID alive. If the audio * session ID is {@link C#AUDIO_SESSION_ID_UNSET} then a new audio session ID is generated. * *

Use of this method is only required on API level 21 and earlier. * * @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} to generate a * new one. * @return The audio session ID. */ private int initializeKeepSessionIdAudioTrack(int audioSessionId) { if (keepSessionIdAudioTrack != null && keepSessionIdAudioTrack.getAudioSessionId() != audioSessionId) { keepSessionIdAudioTrack.release(); keepSessionIdAudioTrack = null; } if (keepSessionIdAudioTrack == null) { int sampleRate = 4000; // Minimum sample rate supported by the platform. int channelConfig = AudioFormat.CHANNEL_OUT_MONO; @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. keepSessionIdAudioTrack = new AudioTrack( C.STREAM_TYPE_DEFAULT, sampleRate, channelConfig, encoding, bufferSize, AudioTrack.MODE_STATIC, audioSessionId); } return keepSessionIdAudioTrack.getAudioSessionId(); } private void updatePriorityTaskManagerForIsLoadingChange(boolean isLoading) { if (priorityTaskManager != null) { if (isLoading && !isPriorityTaskManagerRegistered) { priorityTaskManager.add(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = true; } else if (!isLoading && isPriorityTaskManagerRegistered) { priorityTaskManager.remove(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = false; } } } private static DeviceInfo createDeviceInfo(@Nullable StreamVolumeManager streamVolumeManager) { return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL) .setMinVolume(streamVolumeManager != null ? streamVolumeManager.getMinVolume() : 0) .setMaxVolume(streamVolumeManager != null ? streamVolumeManager.getMaxVolume() : 0) .build(); } private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) { return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY ? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS : PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; } private static final class MediaSourceHolderSnapshot implements MediaSourceInfoHolder { private final Object uid; private Timeline timeline; public MediaSourceHolderSnapshot(Object uid, Timeline timeline) { this.uid = uid; this.timeline = timeline; } @Override public Object getUid() { return uid; } @Override public Timeline getTimeline() { return timeline; } } private final class ComponentListener implements VideoRendererEventListener, AudioRendererEventListener, TextOutput, MetadataOutput, SurfaceHolder.Callback, TextureView.SurfaceTextureListener, SphericalGLSurfaceView.VideoSurfaceListener, AudioFocusManager.PlayerControl, AudioBecomingNoisyManager.EventListener, StreamVolumeManager.Listener, AudioOffloadListener { // VideoRendererEventListener implementation @Override public void onVideoEnabled(DecoderCounters counters) { videoDecoderCounters = counters; analyticsCollector.onVideoEnabled(counters); } @Override public void onVideoDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) { analyticsCollector.onVideoDecoderInitialized( decoderName, initializedTimestampMs, initializationDurationMs); } @Override public void onVideoInputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { videoFormat = format; analyticsCollector.onVideoInputFormatChanged(format, decoderReuseEvaluation); } @Override public void onDroppedFrames(int count, long elapsed) { analyticsCollector.onDroppedFrames(count, elapsed); } @Override public void onVideoSizeChanged(VideoSize newVideoSize) { videoSize = newVideoSize; listeners.sendEvent( EVENT_VIDEO_SIZE_CHANGED, listener -> listener.onVideoSizeChanged(newVideoSize)); } @Override public void onRenderedFirstFrame(Object output, long renderTimeMs) { analyticsCollector.onRenderedFirstFrame(output, renderTimeMs); if (videoOutput == output) { listeners.sendEvent(EVENT_RENDERED_FIRST_FRAME, Listener::onRenderedFirstFrame); } } @Override public void onVideoDecoderReleased(String decoderName) { analyticsCollector.onVideoDecoderReleased(decoderName); } @Override public void onVideoDisabled(DecoderCounters counters) { analyticsCollector.onVideoDisabled(counters); videoFormat = null; videoDecoderCounters = null; } @Override public void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) { analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount); } @Override public void onVideoCodecError(Exception videoCodecError) { analyticsCollector.onVideoCodecError(videoCodecError); } // AudioRendererEventListener implementation @Override public void onAudioEnabled(DecoderCounters counters) { audioDecoderCounters = counters; analyticsCollector.onAudioEnabled(counters); } @Override public void onAudioDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) { analyticsCollector.onAudioDecoderInitialized( decoderName, initializedTimestampMs, initializationDurationMs); } @Override public void onAudioInputFormatChanged( Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { audioFormat = format; analyticsCollector.onAudioInputFormatChanged(format, decoderReuseEvaluation); } @Override public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) { analyticsCollector.onAudioPositionAdvancing(playoutStartSystemTimeMs); } @Override public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { analyticsCollector.onAudioUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); } @Override public void onAudioDecoderReleased(String decoderName) { analyticsCollector.onAudioDecoderReleased(decoderName); } @Override public void onAudioDisabled(DecoderCounters counters) { analyticsCollector.onAudioDisabled(counters); audioFormat = null; audioDecoderCounters = null; } @Override public void onSkipSilenceEnabledChanged(boolean newSkipSilenceEnabled) { if (skipSilenceEnabled == newSkipSilenceEnabled) { return; } skipSilenceEnabled = newSkipSilenceEnabled; listeners.sendEvent( EVENT_SKIP_SILENCE_ENABLED_CHANGED, listener -> listener.onSkipSilenceEnabledChanged(newSkipSilenceEnabled)); } @Override public void onAudioSinkError(Exception audioSinkError) { analyticsCollector.onAudioSinkError(audioSinkError); } @Override public void onAudioCodecError(Exception audioCodecError) { analyticsCollector.onAudioCodecError(audioCodecError); } // TextOutput implementation @Override public void onCues(List cues) { listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cues)); } @Override public void onCues(CueGroup cueGroup) { currentCueGroup = cueGroup; listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cueGroup)); } // MetadataOutput implementation @Override public void onMetadata(Metadata metadata) { staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build(); MediaMetadata newMediaMetadata = buildUpdatedMediaMetadata(); if (!newMediaMetadata.equals(mediaMetadata)) { mediaMetadata = newMediaMetadata; listeners.queueEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(mediaMetadata)); } listeners.queueEvent(EVENT_METADATA, listener -> listener.onMetadata(metadata)); listeners.flushEvents(); } // SurfaceHolder.Callback implementation @Override public void surfaceCreated(SurfaceHolder holder) { if (surfaceHolderSurfaceIsVideoOutput) { setVideoOutputInternal(holder.getSurface()); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { maybeNotifySurfaceSizeChanged(width, height); } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (surfaceHolderSurfaceIsVideoOutput) { setVideoOutputInternal(/* videoOutput= */ null); } maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } // TextureView.SurfaceTextureListener implementation @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { setSurfaceTextureInternal(surfaceTexture); maybeNotifySurfaceSizeChanged(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { maybeNotifySurfaceSizeChanged(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { setVideoOutputInternal(/* videoOutput= */ null); maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { // Do nothing. } // SphericalGLSurfaceView.VideoSurfaceListener @Override public void onVideoSurfaceCreated(Surface surface) { setVideoOutputInternal(surface); } @Override public void onVideoSurfaceDestroyed(Surface surface) { setVideoOutputInternal(/* videoOutput= */ null); } // AudioFocusManager.PlayerControl implementation @Override public void setVolumeMultiplier(float volumeMultiplier) { sendVolumeToRenderers(); } @Override public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) { boolean playWhenReady = getPlayWhenReady(); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); } // AudioBecomingNoisyManager.EventListener implementation. @Override public void onAudioBecomingNoisy() { updatePlayWhenReady( /* playWhenReady= */ false, AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY); } // StreamVolumeManager.Listener implementation. @Override public void onStreamTypeChanged(@C.StreamType int streamType) { DeviceInfo newDeviceInfo = createDeviceInfo(streamVolumeManager); if (!newDeviceInfo.equals(deviceInfo)) { deviceInfo = newDeviceInfo; listeners.sendEvent( EVENT_DEVICE_INFO_CHANGED, listener -> listener.onDeviceInfoChanged(newDeviceInfo)); } } @Override public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) { listeners.sendEvent( EVENT_DEVICE_VOLUME_CHANGED, listener -> listener.onDeviceVolumeChanged(streamVolume, streamMuted)); } // Player.AudioOffloadListener implementation. @Override public void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) { updateWakeAndWifiLock(); } } /** Listeners that are called on the playback thread. */ private static final class FrameMetadataListener implements VideoFrameMetadataListener, CameraMotionListener, PlayerMessage.Target { public static final @MessageType int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; public static final @MessageType int MSG_SET_CAMERA_MOTION_LISTENER = Renderer.MSG_SET_CAMERA_MOTION_LISTENER; public static final @MessageType int MSG_SET_SPHERICAL_SURFACE_VIEW = Renderer.MSG_CUSTOM_BASE; @Nullable private VideoFrameMetadataListener videoFrameMetadataListener; @Nullable private CameraMotionListener cameraMotionListener; @Nullable private VideoFrameMetadataListener internalVideoFrameMetadataListener; @Nullable private CameraMotionListener internalCameraMotionListener; @Override public void handleMessage(@MessageType int messageType, @Nullable Object message) { switch (messageType) { case MSG_SET_VIDEO_FRAME_METADATA_LISTENER: videoFrameMetadataListener = (VideoFrameMetadataListener) message; break; case MSG_SET_CAMERA_MOTION_LISTENER: cameraMotionListener = (CameraMotionListener) message; break; case MSG_SET_SPHERICAL_SURFACE_VIEW: @Nullable SphericalGLSurfaceView surfaceView = (SphericalGLSurfaceView) message; if (surfaceView == null) { internalVideoFrameMetadataListener = null; internalCameraMotionListener = null; } else { internalVideoFrameMetadataListener = surfaceView.getVideoFrameMetadataListener(); internalCameraMotionListener = surfaceView.getCameraMotionListener(); } break; case Renderer.MSG_SET_AUDIO_ATTRIBUTES: case Renderer.MSG_SET_AUDIO_SESSION_ID: case Renderer.MSG_SET_AUX_EFFECT_INFO: case Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY: case Renderer.MSG_SET_SCALING_MODE: case Renderer.MSG_SET_SKIP_SILENCE_ENABLED: case Renderer.MSG_SET_VIDEO_OUTPUT: case Renderer.MSG_SET_VOLUME: case Renderer.MSG_SET_WAKEUP_LISTENER: default: break; } } // VideoFrameMetadataListener @Override public void onVideoFrameAboutToBeRendered( long presentationTimeUs, long releaseTimeNs, Format format, @Nullable MediaFormat mediaFormat) { if (internalVideoFrameMetadataListener != null) { internalVideoFrameMetadataListener.onVideoFrameAboutToBeRendered( presentationTimeUs, releaseTimeNs, format, mediaFormat); } if (videoFrameMetadataListener != null) { videoFrameMetadataListener.onVideoFrameAboutToBeRendered( presentationTimeUs, releaseTimeNs, format, mediaFormat); } } // CameraMotionListener @Override public void onCameraMotion(long timeUs, float[] rotation) { if (internalCameraMotionListener != null) { internalCameraMotionListener.onCameraMotion(timeUs, rotation); } if (cameraMotionListener != null) { cameraMotionListener.onCameraMotion(timeUs, rotation); } } @Override public void onCameraMotionReset() { if (internalCameraMotionListener != null) { internalCameraMotionListener.onCameraMotionReset(); } if (cameraMotionListener != null) { cameraMotionListener.onCameraMotionReset(); } } } @RequiresApi(31) private static final class Api31 { private Api31() {} @DoNotInline public static PlayerId registerMediaMetricsListener( Context context, ExoPlayerImpl player, boolean usePlatformDiagnostics) { @Nullable MediaMetricsListener listener = MediaMetricsListener.create(context); if (listener == null) { Log.w(TAG, ""MediaMetricsService unavailable.""); return new PlayerId(LogSessionId.LOG_SESSION_ID_NONE); } if (usePlatformDiagnostics) { player.addAnalyticsListener(listener); } return new PlayerId(listener.getLogSessionId()); } } } ","oldPeriodIndex " "/* * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.util; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; import android.os.Bundle; import android.util.SparseArray; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Utilities for {@link Bundleable}. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public final class BundleableUtil { /** Converts a list of {@link Bundleable} to a list {@link Bundle}. */ public static ImmutableList toBundleList(List bundleableList) { return toBundleList(bundleableList, Bundleable::toBundle); } /** * Converts a list of {@link Bundleable} to a list {@link Bundle} * * @param bundleableList list of Bundleable items to be converted * @param customToBundleFunc function that specifies how to bundle up each {@link Bundleable} */ public static ImmutableList toBundleList( List bundleableList, Function customToBundleFunc) { ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < bundleableList.size(); i++) { T bundleable = bundleableList.get(i); builder.add(customToBundleFunc.apply(bundleable)); } return builder.build(); } /** Converts a list of {@link Bundle} to a list of {@link Bundleable}. */ public static ImmutableList fromBundleList( Bundleable.Creator [MASK] , List bundleList) { ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < bundleList.size(); i++) { Bundle bundle = checkNotNull(bundleList.get(i)); // Fail fast during parsing. T bundleable = [MASK] .fromBundle(bundle); builder.add(bundleable); } return builder.build(); } /** * Converts a collection of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that * the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList} * conveniently. */ public static ArrayList toBundleArrayList( Collection bundleables) { ArrayList arrayList = new ArrayList<>(bundleables.size()); for (T element : bundleables) { arrayList.add(element.toBundle()); } return arrayList; } /** * Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link * Bundleable}. */ public static SparseArray fromBundleSparseArray( Bundleable.Creator [MASK] , SparseArray bundleSparseArray) { SparseArray result = new SparseArray<>(bundleSparseArray.size()); for (int i = 0; i < bundleSparseArray.size(); i++) { result.put(bundleSparseArray.keyAt(i), [MASK] .fromBundle(bundleSparseArray.valueAt(i))); } return result; } /** * Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link * Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link * Bundle#putSparseParcelableArray} conveniently. */ public static SparseArray toBundleSparseArray( SparseArray bundleableSparseArray) { SparseArray sparseArray = new SparseArray<>(bundleableSparseArray.size()); for (int i = 0; i < bundleableSparseArray.size(); i++) { sparseArray.put(bundleableSparseArray.keyAt(i), bundleableSparseArray.valueAt(i).toBundle()); } return sparseArray; } public static Bundle stringMapToBundle(Map bundleableMap) { Bundle bundle = new Bundle(); for (Map.Entry entry : bundleableMap.entrySet()) { bundle.putString(entry.getKey(), entry.getValue()); } return bundle; } public static HashMap bundleToStringHashMap(Bundle bundle) { HashMap map = new HashMap<>(); if (bundle == Bundle.EMPTY) { return map; } for (String key : bundle.keySet()) { @Nullable String value = bundle.getString(key); if (value != null) { map.put(key, value); } } return map; } public static ImmutableMap bundleToStringImmutableMap(Bundle bundle) { if (bundle == Bundle.EMPTY) { return ImmutableMap.of(); } HashMap map = bundleToStringHashMap(bundle); return ImmutableMap.copyOf(map); } public static Bundle getBundleWithDefault(Bundle bundle, String field, Bundle defaultValue) { @Nullable Bundle result = bundle.getBundle(field); return result != null ? result : defaultValue; } public static ArrayList getIntegerArrayListWithDefault( Bundle bundle, String field, ArrayList defaultValue) { @Nullable ArrayList result = bundle.getIntegerArrayList(field); return result != null ? result : defaultValue; } /** * Sets the application class loader to the given {@link Bundle} if no class loader is present. * *

This assumes that all classes unparceled from {@code bundle} are sharing the class loader of * {@code BundleableUtils}. */ public static void ensureClassLoader(@Nullable Bundle bundle) { if (bundle != null) { bundle.setClassLoader(castNonNull(BundleableUtil.class.getClassLoader())); } } private BundleableUtil() {} } ","creator " "/* * Copyright 2009-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.submitted.multidb; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.mapping.DatabaseIdProvider; public class DummyDatabaseIdProvider implements DatabaseIdProvider { private Properties properties; @Override public String getDatabaseId(DataSource [MASK] ) { return properties.getProperty(""name""); } @Override public void setProperties(Properties p) { this.properties = p; } } ","dataSource " "package jadx.gui.utils; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; import java.util.Vector; import jadx.core.utils.exceptions.JadxRuntimeException; public class NLS { private static final Vector LANG_LOCALES = new Vector<>(); private static final Map LANG_LOCALES_MAP = new HashMap<>(); private static final ResourceBundle FALLBACK_MESSAGES_MAP; private static final LangLocale LOCAL_LOCALE; // Use these two fields to avoid invoking Map.get() method twice. private static ResourceBundle localizedMessagesMap; private static LangLocale currentLocale; static { LOCAL_LOCALE = new LangLocale(Locale.getDefault()); LANG_LOCALES.add(new LangLocale(""en"", ""US"")); // As default language LANG_LOCALES.add(new LangLocale(""zh"", ""CN"")); LANG_LOCALES.add(new LangLocale(""zh"", ""TW"")); LANG_LOCALES.add(new LangLocale(""es"", ""ES"")); LANG_LOCALES.add(new LangLocale(""de"", ""DE"")); LANG_LOCALES.add(new LangLocale(""ko"", ""KR"")); LANG_LOCALES.add(new LangLocale(""pt"", ""BR"")); LANG_LOCALES.add(new LangLocale(""ru"", ""RU"")); LANG_LOCALES.forEach(NLS::load); LangLocale defLang = LANG_LOCALES.get(0); FALLBACK_MESSAGES_MAP = LANG_LOCALES_MAP.get(defLang); localizedMessagesMap = LANG_LOCALES_MAP.get(defLang); } private NLS() { } private static void load(LangLocale locale) { ResourceBundle bundle; ClassLoader classLoader = ClassLoader.getSystemClassLoader(); String resName = String.format(""i18n/Messages_%s.properties"", locale.get()); URL bundleUrl = classLoader.getResource(resName); if (bundleUrl == null) { throw new JadxRuntimeException(""Locale resource not found: "" + resName); } try (Reader reader = new InputStreamReader(bundleUrl.openStream(), StandardCharsets.UTF_8)) { bundle = new PropertyResourceBundle(reader); } catch (IOException e) { throw new JadxRuntimeException(""Failed to load "" + resName, e); } LANG_LOCALES_MAP.put(locale, bundle); } public static String str(String key) { try { return localizedMessagesMap.getString(key); } catch (Exception e) { return FALLBACK_MESSAGES_MAP.getString(key); } } public static String str(String key, Object... [MASK] ) { return String.format(str(key), [MASK] ); } public static String str(String key, LangLocale locale) { ResourceBundle bundle = LANG_LOCALES_MAP.get(locale); if (bundle != null) { try { return bundle.getString(key); } catch (MissingResourceException ignored) { // use fallback string } } return FALLBACK_MESSAGES_MAP.getString(key); // definitely exists } public static void setLocale(LangLocale locale) { if (LANG_LOCALES_MAP.containsKey(locale)) { currentLocale = locale; } else { currentLocale = LANG_LOCALES.get(0); } localizedMessagesMap = LANG_LOCALES_MAP.get(currentLocale); } public static Vector getLangLocales() { return LANG_LOCALES; } public static LangLocale currentLocale() { return currentLocale; } public static LangLocale defaultLocale() { if (LANG_LOCALES_MAP.containsKey(LOCAL_LOCALE)) { return LOCAL_LOCALE; } // fallback to english if unsupported return LANG_LOCALES.get(0); } } ","parameters " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard; import proguard.classfile.*; import proguard.classfile.attribute.annotation.visitor.AllElementValueVisitor; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.constant.visitor.AllConstantVisitor; import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.util.*; import java.io.*; import java.util.*; /** * This class initializes class pools and resource information. * * @author Eric Lafortune */ public class Initializer { private final Configuration configuration; /** * Creates a new Initializer to initialize classes according to the given * configuration. */ public Initializer(Configuration configuration) { this.configuration = configuration; } /** * Initializes the classes in the given program class pool and library class * pool, performs some basic checks, and shrinks the library class pool. */ public void execute(ClassPool programClassPool, ClassPool libraryClassPool) throws IOException { // We're using the system's default character encoding for writing to // the standard output and error output. PrintWriter out = new PrintWriter(System.out, true); PrintWriter err = new PrintWriter(System.err, true); int originalLibraryClassPoolSize = libraryClassPool.size(); // Perform basic checks on the configuration. WarningPrinter fullyQualifiedClassNameNotePrinter = new WarningPrinter(out, configuration.note); FullyQualifiedClassNameChecker fullyQualifiedClassNameChecker = new FullyQualifiedClassNameChecker(programClassPool, libraryClassPool, fullyQualifiedClassNameNotePrinter); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.keep); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoSideEffects); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoExternalSideEffects); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoEscapingParameters); fullyQualifiedClassNameChecker.checkClassSpecifications(configuration.assumeNoExternalReturnValues); StringMatcher keepAttributesMatcher = configuration.keepAttributes != null ? new ListParser(new NameParser()).parse(configuration.keepAttributes) : new EmptyStringMatcher(); WarningPrinter getAnnotationNotePrinter = new WarningPrinter(out, configuration.note); if (!keepAttributesMatcher.matches(ClassConstants.ATTR_RuntimeVisibleAnnotations)) { programClassPool.classesAccept( new AllConstantVisitor( new GetAnnotationChecker(getAnnotationNotePrinter))); } WarningPrinter getSignatureNotePrinter = new WarningPrinter(out, configuration.note); if (!keepAttributesMatcher.matches(ClassConstants.ATTR_Signature)) { programClassPool.classesAccept( new AllConstantVisitor( new GetSignatureChecker(getSignatureNotePrinter))); } WarningPrinter getEnclosingClassNotePrinter = new WarningPrinter(out, configuration.note); if (!keepAttributesMatcher.matches(ClassConstants.ATTR_InnerClasses)) { programClassPool.classesAccept( new AllConstantVisitor( new GetEnclosingClassChecker(getEnclosingClassNotePrinter))); } WarningPrinter getEnclosingMethodNotePrinter = new WarningPrinter(out, configuration.note); if (!keepAttributesMatcher.matches(ClassConstants.ATTR_EnclosingMethod)) { programClassPool.classesAccept( new AllConstantVisitor( new GetEnclosingMethodChecker(getEnclosingMethodNotePrinter))); } // Construct a reduced library class pool with only those library // classes whose hierarchies are referenced by the program classes. // We can't do this if we later have to come up with the obfuscated // class member names that are globally unique. ClassPool reducedLibraryClassPool = configuration.useUniqueClassMemberNames ? null : new ClassPool(); WarningPrinter classReferenceWarningPrinter = new WarningPrinter(err, configuration.warn); WarningPrinter dependencyWarningPrinter = new WarningPrinter(err, configuration.warn); // Initialize the superclass hierarchies for program classes. programClassPool.classesAccept( new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool, classReferenceWarningPrinter, null)); // Initialize the superclass hierarchy of all library classes, without // warnings. libraryClassPool.classesAccept( new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool, null, dependencyWarningPrinter)); // Initialize the class references of program class members and // attributes. Note that all superclass hierarchies have to be // initialized for this purpose. WarningPrinter programMemberReferenceWarningPrinter = new WarningPrinter(err, configuration.warn); WarningPrinter libraryMemberReferenceWarningPrinter = new WarningPrinter(err, configuration.warn); programClassPool.classesAccept( new ClassReferenceInitializer(programClassPool, libraryClassPool, classReferenceWarningPrinter, programMemberReferenceWarningPrinter, libraryMemberReferenceWarningPrinter, null)); if (reducedLibraryClassPool != null) { // Collect the library classes that are directly referenced by // program classes, without reflection. programClassPool.classesAccept( new ReferencedClassVisitor( new LibraryClassFilter( new ClassPoolFiller(reducedLibraryClassPool)))); // Reinitialize the superclass hierarchies of referenced library // classes, this time with warnings. reducedLibraryClassPool.classesAccept( new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool, classReferenceWarningPrinter, null)); } // Initialize the enum annotation references. programClassPool.classesAccept( new AllAttributeVisitor(true, new AllElementValueVisitor(true, new EnumFieldReferenceInitializer()))); // Initialize the Class.forName references. WarningPrinter dynamicClassReferenceNotePrinter = new WarningPrinter(out, configuration.note); WarningPrinter classForNameNotePrinter = new WarningPrinter(out, configuration.note); programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new AllInstructionVisitor( new DynamicClassReferenceInitializer(programClassPool, libraryClassPool, dynamicClassReferenceNotePrinter, null, classForNameNotePrinter, createClassNoteExceptionMatcher(configuration.keep, true)))))); // Initialize the WebView.addJavascriptInterface references. WarningPrinter webViewClassReferenceNotePrinter = new WarningPrinter(out, configuration.note); // Initialize the Class.get[Declared]{Field,Method} references. WarningPrinter getMemberNotePrinter = new WarningPrinter(out, configuration.note); programClassPool.classesAccept( new AllMethodVisitor( new AllAttributeVisitor( new DynamicMemberReferenceInitializer(programClassPool, libraryClassPool, getMemberNotePrinter, createClassMemberNoteExceptionMatcher(configuration.keep, true), createClassMemberNoteExceptionMatcher(configuration.keep, false))))); // Initialize other string constant references, if requested. if (configuration.adaptClassStrings != null) { programClassPool.classesAccept( new ClassNameFilter(configuration.adaptClassStrings, new AllConstantVisitor( new StringReferenceInitializer(programClassPool, libraryClassPool)))); } // Initialize the class references of library class members. if (reducedLibraryClassPool != null) { // Collect the library classes that are referenced by program // classes, directly or indirectly, with or without reflection. programClassPool.classesAccept( new ReferencedClassVisitor( new LibraryClassFilter( new ClassHierarchyTraveler(true, true, true, false, new LibraryClassFilter( new ClassPoolFiller(reducedLibraryClassPool)))))); // Initialize the class references of referenced library // classes, without warnings. reducedLibraryClassPool.classesAccept( new ClassReferenceInitializer(programClassPool, libraryClassPool, null, null, null, dependencyWarningPrinter)); // Reset the library class pool. libraryClassPool.clear(); // Copy the library classes that are referenced directly by program // classes and the library classes that are referenced by referenced // library classes. reducedLibraryClassPool.classesAccept( new MultiClassVisitor( new ClassHierarchyTraveler(true, true, true, false, new LibraryClassFilter( new ClassPoolFiller(libraryClassPool))), new ReferencedClassVisitor( new LibraryClassFilter( new ClassHierarchyTraveler(true, true, true, false, new LibraryClassFilter( new ClassPoolFiller(libraryClassPool))))) )); } else { // Initialize the class references of all library class members. libraryClassPool.classesAccept( new ClassReferenceInitializer(programClassPool, libraryClassPool, null, null, null, dependencyWarningPrinter)); } // Initialize the subclass hierarchies. programClassPool.classesAccept(new ClassSubHierarchyInitializer()); libraryClassPool.classesAccept(new ClassSubHierarchyInitializer()); // Share strings between the classes, to reduce heap memory usage. programClassPool.classesAccept(new StringSharer()); libraryClassPool.classesAccept(new StringSharer()); // Check for any unmatched class members. WarningPrinter classMemberNotePrinter = new WarningPrinter(out, configuration.note); ClassMemberChecker classMemberChecker = new ClassMemberChecker(programClassPool, classMemberNotePrinter); classMemberChecker.checkClassSpecifications(configuration.keep); classMemberChecker.checkClassSpecifications(configuration.assumeNoSideEffects); classMemberChecker.checkClassSpecifications(configuration.assumeNoExternalSideEffects); classMemberChecker.checkClassSpecifications(configuration.assumeNoEscapingParameters); classMemberChecker.checkClassSpecifications(configuration.assumeNoExternalReturnValues); // Check for unkept descriptor classes of kept class members. WarningPrinter descriptorKeepNotePrinter = new WarningPrinter(out, configuration.note); new DescriptorKeepChecker(programClassPool, libraryClassPool, descriptorKeepNotePrinter).checkClassSpecifications(configuration.keep); // Check for keep options that only match library classes. WarningPrinter libraryKeepNotePrinter = new WarningPrinter(out, configuration.note); new LibraryKeepChecker(programClassPool, libraryClassPool, libraryKeepNotePrinter).checkClassSpecifications(configuration.keep); // Print out a summary of the notes, if necessary. int fullyQualifiedNoteCount = fullyQualifiedClassNameNotePrinter.getWarningCount(); if (fullyQualifiedNoteCount > 0) { out.println(""Note: there were "" + fullyQualifiedNoteCount + "" references to unknown classes.""); out.println("" You should check your configuration for typos.""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#unknownclass)""); } int classMemberNoteCount = classMemberNotePrinter.getWarningCount(); if (classMemberNoteCount > 0) { out.println(""Note: there were "" + classMemberNoteCount + "" references to unknown class members.""); out.println("" You should check your configuration for typos.""); } int getAnnotationNoteCount = getAnnotationNotePrinter.getWarningCount(); if (getAnnotationNoteCount > 0) { out.println(""Note: there were "" + getAnnotationNoteCount + "" classes trying to access annotations using reflection.""); out.println("" You should consider keeping the annotation attributes""); out.println("" (using '-keepattributes *Annotation*').""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#attributes)""); } int getSignatureNoteCount = getSignatureNotePrinter.getWarningCount(); if (getSignatureNoteCount > 0) { out.println(""Note: there were "" + getSignatureNoteCount + "" classes trying to access generic signatures using reflection.""); out.println("" You should consider keeping the signature attributes""); out.println("" (using '-keepattributes Signature').""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#attributes)""); } int getEnclosingClassNoteCount = getEnclosingClassNotePrinter.getWarningCount(); if (getEnclosingClassNoteCount > 0) { out.println(""Note: there were "" + getEnclosingClassNoteCount + "" classes trying to access enclosing classes using reflection.""); out.println("" You should consider keeping the inner classes attributes""); out.println("" (using '-keepattributes InnerClasses').""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#attributes)""); } int getEnclosingMethodNoteCount = getEnclosingMethodNotePrinter.getWarningCount(); if (getEnclosingMethodNoteCount > 0) { out.println(""Note: there were "" + getEnclosingMethodNoteCount + "" classes trying to access enclosing methods using reflection.""); out.println("" You should consider keeping the enclosing method attributes""); out.println("" (using '-keepattributes InnerClasses,EnclosingMethod').""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#attributes)""); } int descriptorNoteCount = descriptorKeepNotePrinter.getWarningCount(); if (descriptorNoteCount > 0) { out.println(""Note: there were "" + descriptorNoteCount + "" unkept descriptor classes in kept class members.""); out.println("" You should consider explicitly keeping the mentioned classes""); out.println("" (using '-keep').""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#descriptorclass)""); } int libraryNoteCount = libraryKeepNotePrinter.getWarningCount(); if (libraryNoteCount > 0) { out.println(""Note: there were "" + libraryNoteCount + "" library classes explicitly being kept.""); out.println("" You don't need to keep library classes; they are already left unchanged.""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#libraryclass)""); } int dynamicClassReferenceNoteCount = dynamicClassReferenceNotePrinter.getWarningCount(); if (dynamicClassReferenceNoteCount > 0) { out.println(""Note: there were "" + dynamicClassReferenceNoteCount + "" unresolved dynamic references to classes or interfaces.""); out.println("" You should check if you need to specify additional program jars.""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclass)""); } int classForNameNoteCount = classForNameNotePrinter.getWarningCount(); if (classForNameNoteCount > 0) { out.println(""Note: there were "" + classForNameNoteCount + "" class casts of dynamically created class instances.""); out.println("" You might consider explicitly keeping the mentioned classes and/or""); out.println("" their implementations (using '-keep').""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclasscast)""); } int getmemberNoteCount = getMemberNotePrinter.getWarningCount(); if (getmemberNoteCount > 0) { out.println(""Note: there were "" + getmemberNoteCount + "" accesses to class members by means of reflection.""); out.println("" You should consider explicitly keeping the mentioned class members""); out.println("" (using '-keep' or '-keepclassmembers').""); out.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclassmember)""); } // Print out a summary of the warnings, if necessary. int classReferenceWarningCount = classReferenceWarningPrinter.getWarningCount(); if (classReferenceWarningCount > 0) { err.println(""Warning: there were "" + classReferenceWarningCount + "" unresolved references to classes or interfaces.""); err.println("" You may need to add missing library jars or update their versions.""); err.println("" If your code works fine without the missing classes, you can suppress""); err.println("" the warnings with '-dontwarn' options.""); if (configuration.skipNonPublicLibraryClasses) { err.println("" You may also have to remove the option '-skipnonpubliclibraryclasses'.""); } err.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass)""); } int dependencyWarningCount = dependencyWarningPrinter.getWarningCount(); if (dependencyWarningCount > 0) { err.println(""Warning: there were "" + dependencyWarningCount + "" instances of library classes depending on program classes.""); err.println("" You must avoid such dependencies, since the program classes will""); err.println("" be processed, while the library classes will remain unchanged.""); err.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#dependency)""); } int programMemberReferenceWarningCount = programMemberReferenceWarningPrinter.getWarningCount(); if (programMemberReferenceWarningCount > 0) { err.println(""Warning: there were "" + programMemberReferenceWarningCount + "" unresolved references to program class members.""); err.println("" Your input classes appear to be inconsistent.""); err.println("" You may need to recompile the code.""); err.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedprogramclassmember)""); } int libraryMemberReferenceWarningCount = libraryMemberReferenceWarningPrinter.getWarningCount(); if (libraryMemberReferenceWarningCount > 0) { err.println(""Warning: there were "" + libraryMemberReferenceWarningCount + "" unresolved references to library class members.""); err.println("" You probably need to update the library versions.""); if (!configuration.skipNonPublicLibraryClassMembers) { err.println("" Alternatively, you may have to specify the option ""); err.println("" '-dontskipnonpubliclibraryclassmembers'.""); } if (configuration.skipNonPublicLibraryClasses) { err.println("" You may also have to remove the option '-skipnonpubliclibraryclasses'.""); } err.println("" (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedlibraryclassmember)""); } if ((classReferenceWarningCount > 0 || dependencyWarningCount > 0 || programMemberReferenceWarningCount > 0 || libraryMemberReferenceWarningCount > 0) && !configuration.ignoreWarnings) { throw new IOException(""Please correct the above warnings first.""); } if ((configuration.note == null || !configuration.note.isEmpty()) && (configuration.warn != null && configuration.warn.isEmpty() || configuration.ignoreWarnings)) { out.println(""Note: you're ignoring all warnings!""); } // Discard unused library classes. if (configuration.verbose) { out.println(""Ignoring unused library classes...""); out.println("" Original number of library classes: "" + originalLibraryClassPoolSize); out.println("" Final number of library classes: "" + libraryClassPool.size()); } } /** * Extracts a list of exceptions of classes for which not to print notes, * from the keep configuration. */ private StringMatcher createClassNoteExceptionMatcher(List noteExceptions, boolean markClasses) { if (noteExceptions != null) { List noteExceptionNames = new ArrayList(noteExceptions.size()); for (int index = 0; index < noteExceptions.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)noteExceptions.get(index); if (keepClassSpecification.markClasses || !markClasses) { // If the class itself is being kept, it's ok. String className = keepClassSpecification.className; if (className != null && !containsWildCardReferences(className)) { noteExceptionNames.add(className); } // If all of its extensions are being kept, it's ok too. String extendsClassName = keepClassSpecification.extendsClassName; if (extendsClassName != null && !containsWildCardReferences(extendsClassName)) { noteExceptionNames.add(extendsClassName); } } } if (noteExceptionNames.size() > 0) { return new ListParser(new ClassNameParser()).parse(noteExceptionNames); } } return null; } /** * Extracts a list of exceptions of field or method names for which not to * print notes, from the keep configuration. */ private StringMatcher createClassMemberNoteExceptionMatcher(List noteExceptions, boolean [MASK] ) { if (noteExceptions != null) { List noteExceptionNames = new ArrayList(); for (int index = 0; index < noteExceptions.size(); index++) { KeepClassSpecification keepClassSpecification = (KeepClassSpecification)noteExceptions.get(index); List memberSpecifications = [MASK] ? keepClassSpecification.fieldSpecifications : keepClassSpecification.methodSpecifications; if (memberSpecifications != null) { for (int index2 = 0; index2 < memberSpecifications.size(); index2++) { MemberSpecification memberSpecification = (MemberSpecification)memberSpecifications.get(index2); String memberName = memberSpecification.name; if (memberName != null && !containsWildCardReferences(memberName)) { noteExceptionNames.add(memberName); } } } } if (noteExceptionNames.size() > 0) { return new ListParser(new NameParser()).parse(noteExceptionNames); } } return null; } /** * Returns whether the given string contains a numeric reference to a * wild card (""""). */ private static boolean containsWildCardReferences(String string) { int openIndex = string.indexOf('<'); if (openIndex < 0) { return false; } int closeIndex = string.indexOf('>', openIndex + 1); if (closeIndex < 0) { return false; } try { Integer.parseInt(string.substring(openIndex + 1, closeIndex)); } catch (NumberFormatException e) { return false; } return true; } } ","isField " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 2014-2016, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ package android.icu.dev.test.util; import org.junit.Test; import android.icu.dev.test.TestFmwk; import android.icu.text.MessageFormat; import android.icu.text.SimpleFormatter; import android.icu.util.ULocale; public class SimpleFormatterTest extends TestFmwk { /** * Constructor */ public SimpleFormatterTest() { } // public methods ----------------------------------------------- @Test public void TestWithNoArguments() { SimpleFormatter fmt = SimpleFormatter.compile(""This doesn''t have templates '{0}""); assertEquals( ""getArgumentLimit"", 0, fmt.getArgumentLimit()); assertEquals( ""format"", ""This doesn't have templates {0}"", fmt.format(""unused"")); assertEquals( ""format with values=null"", ""This doesn't have templates {0}"", fmt.format((CharSequence[])null)); assertEquals( ""toString"", ""This doesn't have templates {0}"", fmt.toString()); int[] [MASK] = new int[1]; assertEquals( ""formatAndAppend"", ""This doesn't have templates {0}"", fmt.formatAndAppend(new StringBuilder(), [MASK] ).toString()); assertEquals( "" [MASK] [0]"", -1, [MASK] [0]); assertEquals( ""formatAndAppend with values=null"", ""This doesn't have templates {0}"", fmt.formatAndAppend(new StringBuilder(), null, (CharSequence[])null).toString()); assertEquals( ""formatAndReplace with values=null"", ""This doesn't have templates {0}"", fmt.formatAndReplace(new StringBuilder(), null, (CharSequence[])null).toString()); } @Test public void TestSyntaxErrors() { try { SimpleFormatter.compile(""{}""); fail(""Syntax error did not yield an exception.""); } catch (IllegalArgumentException expected) { } try { SimpleFormatter.compile(""{12d""); fail(""Syntax error did not yield an exception.""); } catch (IllegalArgumentException expected) { } } @Test public void TestOneArgument() { assertEquals(""TestOneArgument"", ""1 meter"", SimpleFormatter.compile(""{0} meter"").format(""1"")); } @Test public void TestBigArgument() { SimpleFormatter fmt = SimpleFormatter.compile(""a{20}c""); assertEquals(""{20} count"", 21, fmt.getArgumentLimit()); CharSequence[] values = new CharSequence[21]; values[20] = ""b""; assertEquals(""{20}=b"", ""abc"", fmt.format(values)); } @Test public void TestGetTextWithNoArguments() { assertEquals( """", ""Templates and are here."", SimpleFormatter.compile( ""Templates {1}{2} and {3} are here."").getTextWithNoArguments()); } @Test public void TestTooFewArgumentValues() { SimpleFormatter fmt = SimpleFormatter.compile( ""Templates {2}{1} and {4} are out of order.""); try { fmt.format(""freddy"", ""tommy"", ""frog"", ""leg""); fail(""Expected IllegalArgumentException""); } catch (IllegalArgumentException e) { // Expected } try { fmt.formatAndAppend( new StringBuilder(), null, ""freddy"", ""tommy"", ""frog"", ""leg""); fail(""Expected IllegalArgumentException""); } catch (IllegalArgumentException e) { // Expected } try { fmt.formatAndReplace( new StringBuilder(), null, ""freddy"", ""tommy"", ""frog"", ""leg""); fail(""Expected IllegalArgumentException""); } catch (IllegalArgumentException e) { // Expected } } @Test public void TestWithArguments() { SimpleFormatter fmt = SimpleFormatter.compile( ""Templates {2}{1} and {4} are out of order.""); assertEquals( ""getArgumentLimit"", 5, fmt.getArgumentLimit()); assertEquals( ""toString"", ""Templates {2}{1} and {4} are out of order."", fmt.toString()); int[] [MASK] = new int[6]; assertEquals( ""format"", ""123456: Templates frogtommy and {0} are out of order."", fmt.formatAndAppend( new StringBuilder(""123456: ""), [MASK] , ""freddy"", ""tommy"", ""frog"", ""leg"", ""{0}"").toString()); int[] expectedOffsets = {-1, 22, 18, -1, 32, -1}; verifyOffsets(expectedOffsets, [MASK] ); } @Test public void TestFormatUseAppendToAsArgument() { SimpleFormatter fmt = SimpleFormatter.compile( ""Arguments {0} and {1}""); StringBuilder appendTo = new StringBuilder(""previous:""); try { fmt.formatAndAppend(appendTo, null, appendTo, ""frog""); fail(""IllegalArgumentException expected.""); } catch (IllegalArgumentException e) { // expected. } } @Test public void TestFormatReplaceNoOptimization() { SimpleFormatter fmt = SimpleFormatter.compile(""{2}, {0}, {1} and {3}""); int[] [MASK] = new int[4]; StringBuilder result = new StringBuilder(""original""); assertEquals( ""format"", ""frog, original, freddy and by"", fmt.formatAndReplace( result, [MASK] , result, ""freddy"", ""frog"", ""by"").toString()); int[] expectedOffsets = {6, 16, 0, 27}; verifyOffsets(expectedOffsets, [MASK] ); } @Test public void TestFormatReplaceNoOptimizationLeadingText() { SimpleFormatter fmt = SimpleFormatter.compile(""boo {2}, {0}, {1} and {3}""); int[] [MASK] = new int[4]; StringBuilder result = new StringBuilder(""original""); assertEquals( ""format"", ""boo original, freddy, frog and by"", fmt.formatAndReplace( result, [MASK] , ""freddy"", ""frog"", result, ""by"").toString()); int[] expectedOffsets = {14, 22, 4, 31}; verifyOffsets(expectedOffsets, [MASK] ); } @Test public void TestFormatReplaceOptimization() { SimpleFormatter fmt = SimpleFormatter.compile(""{2}, {0}, {1} and {3}""); int[] [MASK] = new int[4]; StringBuilder result = new StringBuilder(""original""); assertEquals( ""format"", ""original, freddy, frog and by"", fmt.formatAndReplace( result, [MASK] , ""freddy"", ""frog"", result, ""by"").toString()); int[] expectedOffsets = {10, 18, 0, 27}; verifyOffsets(expectedOffsets, [MASK] ); } @Test public void TestFormatReplaceOptimizationNoOffsets() { SimpleFormatter fmt = SimpleFormatter.compile(""{2}, {0}, {1} and {3}""); StringBuilder result = new StringBuilder(""original""); assertEquals( ""format"", ""original, freddy, frog and by"", fmt.formatAndReplace( result, null, ""freddy"", ""frog"", result, ""by"").toString()); } @Test public void TestFormatReplaceNoOptimizationNoOffsets() { SimpleFormatter fmt = SimpleFormatter.compile( ""Arguments {0} and {1}""); StringBuilder result = new StringBuilder(""previous:""); assertEquals( """", ""Arguments previous: and frog"", fmt.formatAndReplace(result, null, result, ""frog"").toString()); } @Test public void TestFormatReplaceNoOptimizationLeadingArgumentUsedTwice() { SimpleFormatter fmt = SimpleFormatter.compile( ""{2}, {0}, {1} and {3} {2}""); StringBuilder result = new StringBuilder(""original""); int[] [MASK] = new int[4]; assertEquals( """", ""original, freddy, frog and by original"", fmt.formatAndReplace( result, [MASK] , ""freddy"", ""frog"", result, ""by"").toString()); int[] expectedOffsets = {10, 18, 30, 27}; verifyOffsets(expectedOffsets, [MASK] ); } @Test public void TestQuotingLikeMessageFormat() { String pattern = ""{0} don't can''t '{5}''}{a' again '}'{1} to the '{end""; SimpleFormatter spf = SimpleFormatter.compile(pattern); MessageFormat mf = new MessageFormat(pattern, ULocale.ROOT); String expected = ""X don't can't {5}'}{a again }Y to the {end""; assertEquals(""MessageFormat"", expected, mf.format(new Object[] { ""X"", ""Y"" })); assertEquals(""SimpleFormatter"", expected, spf.format(""X"", ""Y"")); } private void verifyOffsets(int[] expected, int[] actual) { for (int i = 0; i < expected.length; ++i) { if (expected[i] != actual[i]) { errln(""Expected ""+expected[i]+"", got "" + actual[i]); } } } } ","offsets " "/******************************************************************************* * Copyright (c) 2013, Daniel Murphy * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS"" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ package org.jbox2d.dynamics; import org.jbox2d.callbacks.ContactImpulse; import org.jbox2d.callbacks.ContactListener; import org.jbox2d.common.MathUtils; import org.jbox2d.common.Settings; import org.jbox2d.common.Sweep; import org.jbox2d.common.Timer; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.contacts.Contact; import org.jbox2d.dynamics.contacts.ContactSolver; import org.jbox2d.dynamics.contacts.ContactSolver.ContactSolverDef; import org.jbox2d.dynamics.contacts.ContactVelocityConstraint; import org.jbox2d.dynamics.contacts.Position; import org.jbox2d.dynamics.contacts.Velocity; import org.jbox2d.dynamics.joints.Joint; /* Position Correction Notes ========================= I tried the several algorithms for position correction of the 2D revolute joint. I looked at these systems: - simple pendulum (1m diameter sphere on massless 5m stick) with initial angular velocity of 100 rad/s. - suspension bridge with 30 1m long planks of length 1m. - multi-link chain with 30 1m long links. Here are the algorithms: Baumgarte - A fraction of the position error is added to the velocity error. There is no separate position solver. Pseudo Velocities - After the velocity solver and position integration, the position error, Jacobian, and effective mass are recomputed. Then the velocity constraints are solved with pseudo velocities and a fraction of the position error is added to the pseudo velocity error. The pseudo velocities are initialized to zero and there is no warm-starting. After the position solver, the pseudo velocities are added to the positions. This is also called the First Order World method or the Position LCP method. Modified Nonlinear Gauss-Seidel (NGS) - Like Pseudo Velocities except the position error is re-computed for each raint and the positions are updated after the raint is solved. The radius vectors (aka Jacobians) are re-computed too (otherwise the algorithm has horrible instability). The pseudo velocity states are not needed because they are effectively zero at the beginning of each iteration. Since we have the current position error, we allow the iterations to terminate early if the error becomes smaller than Settings.linearSlop. Full NGS or just NGS - Like Modified NGS except the effective mass are re-computed each time a raint is solved. Here are the results: Baumgarte - this is the cheapest algorithm but it has some stability problems, especially with the bridge. The chain links separate easily close to the root and they jitter as they struggle to pull together. This is one of the most common methods in the field. The big drawback is that the position correction artificially affects the momentum, thus leading to instabilities and false bounce. I used a bias factor of 0.2. A larger bias factor makes the bridge less stable, a smaller factor makes joints and contacts more spongy. Pseudo Velocities - the is more stable than the Baumgarte method. The bridge is stable. However, joints still separate with large angular velocities. Drag the simple pendulum in a circle quickly and the joint will separate. The chain separates easily and does not recover. I used a bias factor of 0.2. A larger value lead to the bridge collapsing when a heavy cube drops on it. Modified NGS - this algorithm is better in some ways than Baumgarte and Pseudo Velocities, but in other ways it is worse. The bridge and chain are much more stable, but the simple pendulum goes unstable at high angular velocities. Full NGS - stable in all tests. The joints display good stiffness. The bridge still sags, but this is better than infinite forces. Recommendations Pseudo Velocities are not really worthwhile because the bridge and chain cannot recover from joint separation. In other cases the benefit over Baumgarte is small. Modified NGS is not a robust method for the revolute joint due to the violent instability seen in the simple pendulum. Perhaps it is viable with other raint types, especially scalar constraints where the effective mass is a scalar. This leaves Baumgarte and Full NGS. Baumgarte has small, but manageable instabilities and is very fast. I don't think we can escape Baumgarte, especially in highly demanding cases where high raint fidelity is not needed. Full NGS is robust and easy on the eyes. I recommend this as an option for higher fidelity simulation and certainly for suspension bridges and long chains. Full NGS might be a good choice for ragdolls, especially motorized ragdolls where joint separation can be problematic. The number of NGS iterations can be reduced for better performance without harming robustness much. Each joint in a can be handled differently in the position solver. So I recommend a system where the user can select the algorithm on a per joint basis. I would probably default to the slower Full NGS and let the user select the faster Baumgarte method in performance critical scenarios. */ /* Cache Performance The Box2D solvers are dominated by cache misses. Data structures are designed to increase the number of cache hits. Much of misses are due to random access to body data. The raint structures are iterated over linearly, which leads to few cache misses. The bodies are not accessed during iteration. Instead read only data, such as the mass values are stored with the constraints. The mutable data are the raint impulses and the bodies velocities/positions. The impulses are held inside the raint structures. The body velocities/positions are held in compact, temporary arrays to increase the number of cache hits. Linear and angular velocity are stored in a single array since multiple arrays lead to multiple misses. */ /* 2D Rotation R = [cos(theta) -sin(theta)] [sin(theta) cos(theta) ] thetaDot = omega Let q1 = cos(theta), q2 = sin(theta). R = [q1 -q2] [q2 q1] q1Dot = -thetaDot * q2 q2Dot = thetaDot * q1 q1_new = q1_old - dt * w * q2 q2_new = q2_old + dt * w * q1 then normalize. This might be faster than computing sin+cos. However, we can compute sin+cos of the same angle fast. */ /** This is an internal class. * * @author Daniel Murphy */ public class Island { public ContactListener m_listener; public Body[] m_bodies; public Contact[] m_contacts; public Joint[] m_joints; public Position[] m_positions; public Velocity[] m_velocities; public int m_bodyCount; public int m_jointCount; public int m_contactCount; public int m_bodyCapacity; public int m_contactCapacity; public int m_ [MASK] ; public Island () { } public void init (int bodyCapacity, int contactCapacity, int [MASK] , ContactListener listener) { // System.out.println(""Initializing Island""); m_bodyCapacity = bodyCapacity; m_contactCapacity = contactCapacity; m_ [MASK] = [MASK] ; m_bodyCount = 0; m_contactCount = 0; m_jointCount = 0; m_listener = listener; if (m_bodies == null || m_bodyCapacity > m_bodies.length) { m_bodies = new Body[m_bodyCapacity]; } if (m_joints == null || m_ [MASK] > m_joints.length) { m_joints = new Joint[m_ [MASK] ]; } if (m_contacts == null || m_contactCapacity > m_contacts.length) { m_contacts = new Contact[m_contactCapacity]; } // dynamic array if (m_velocities == null || m_bodyCapacity > m_velocities.length) { final Velocity[] old = m_velocities == null ? new Velocity[0] : m_velocities; m_velocities = new Velocity[m_bodyCapacity]; System.arraycopy(old, 0, m_velocities, 0, old.length); for (int i = old.length; i < m_velocities.length; i++) { m_velocities[i] = new Velocity(); } } // dynamic array if (m_positions == null || m_bodyCapacity > m_positions.length) { final Position[] old = m_positions == null ? new Position[0] : m_positions; m_positions = new Position[m_bodyCapacity]; System.arraycopy(old, 0, m_positions, 0, old.length); for (int i = old.length; i < m_positions.length; i++) { m_positions[i] = new Position(); } } } public void clear () { m_bodyCount = 0; m_contactCount = 0; m_jointCount = 0; } private final ContactSolver contactSolver = new ContactSolver(); private final Timer timer = new Timer(); private final SolverData solverData = new SolverData(); private final ContactSolverDef solverDef = new ContactSolverDef(); public void solve (Profile profile, TimeStep step, Vec2 gravity, boolean allowSleep) { // System.out.println(""Solving Island""); float h = step.dt; // Integrate velocities and apply damping. Initialize the body state. for (int i = 0; i < m_bodyCount; ++i) { final Body b = m_bodies[i]; final Sweep bm_sweep = b.m_sweep; final Vec2 c = bm_sweep.c; float a = bm_sweep.a; final Vec2 v = b.m_linearVelocity; float w = b.m_angularVelocity; // Store positions for continuous collision. bm_sweep.c0.set(bm_sweep.c); bm_sweep.a0 = bm_sweep.a; if (b.m_type == BodyType.DYNAMIC) { // Integrate velocities. // v += h * (b.m_gravityScale * gravity + b.m_invMass * b.m_force); v.x += h * (b.m_gravityScale * gravity.x + b.m_invMass * b.m_force.x); v.y += h * (b.m_gravityScale * gravity.y + b.m_invMass * b.m_force.y); w += h * b.m_invI * b.m_torque; // Apply damping. // ODE: dv/dt + c * v = 0 // Solution: v(t) = v0 * exp(-c * t) // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * // exp(-c * dt) // v2 = exp(-c * dt) * v1 // Pade approximation: // v2 = v1 * 1 / (1 + c * dt) v.x *= 1.0f / (1.0f + h * b.m_linearDamping); v.y *= 1.0f / (1.0f + h * b.m_linearDamping); w *= 1.0f / (1.0f + h * b.m_angularDamping); } m_positions[i].c.x = c.x; m_positions[i].c.y = c.y; m_positions[i].a = a; m_velocities[i].v.x = v.x; m_velocities[i].v.y = v.y; m_velocities[i].w = w; } timer.reset(); // Solver data solverData.step = step; solverData.positions = m_positions; solverData.velocities = m_velocities; // Initialize velocity constraints. solverDef.step = step; solverDef.contacts = m_contacts; solverDef.count = m_contactCount; solverDef.positions = m_positions; solverDef.velocities = m_velocities; contactSolver.init(solverDef); // System.out.println(""island init vel""); contactSolver.initializeVelocityConstraints(); if (step.warmStarting) { // System.out.println(""island warm start""); contactSolver.warmStart(); } for (int i = 0; i < m_jointCount; ++i) { m_joints[i].initVelocityConstraints(solverData); } profile.solveInit.accum(timer.getMilliseconds()); // Solve velocity constraints timer.reset(); // System.out.println(""island solving velocities""); for (int i = 0; i < step.velocityIterations; ++i) { for (int j = 0; j < m_jointCount; ++j) { m_joints[j].solveVelocityConstraints(solverData); } contactSolver.solveVelocityConstraints(); } // Store impulses for warm starting contactSolver.storeImpulses(); profile.solveVelocity.accum(timer.getMilliseconds()); // Integrate positions for (int i = 0; i < m_bodyCount; ++i) { final Vec2 c = m_positions[i].c; float a = m_positions[i].a; final Vec2 v = m_velocities[i].v; float w = m_velocities[i].w; // Check for large velocities float translationx = v.x * h; float translationy = v.y * h; if (translationx * translationx + translationy * translationy > Settings.maxTranslationSquared) { float ratio = Settings.maxTranslation / MathUtils.sqrt(translationx * translationx + translationy * translationy); v.x *= ratio; v.y *= ratio; } float rotation = h * w; if (rotation * rotation > Settings.maxRotationSquared) { float ratio = Settings.maxRotation / MathUtils.abs(rotation); w *= ratio; } // Integrate c.x += h * v.x; c.y += h * v.y; a += h * w; m_positions[i].a = a; m_velocities[i].w = w; } // Solve position constraints timer.reset(); boolean positionSolved = false; for (int i = 0; i < step.positionIterations; ++i) { boolean contactsOkay = contactSolver.solvePositionConstraints(); boolean jointsOkay = true; for (int j = 0; j < m_jointCount; ++j) { boolean jointOkay = m_joints[j].solvePositionConstraints(solverData); jointsOkay = jointsOkay && jointOkay; } if (contactsOkay && jointsOkay) { // Exit early if the position errors are small. positionSolved = true; break; } } // Copy state buffers back to the bodies for (int i = 0; i < m_bodyCount; ++i) { Body body = m_bodies[i]; body.m_sweep.c.x = m_positions[i].c.x; body.m_sweep.c.y = m_positions[i].c.y; body.m_sweep.a = m_positions[i].a; body.m_linearVelocity.x = m_velocities[i].v.x; body.m_linearVelocity.y = m_velocities[i].v.y; body.m_angularVelocity = m_velocities[i].w; body.synchronizeTransform(); } profile.solvePosition.accum(timer.getMilliseconds()); report(contactSolver.m_velocityConstraints); if (allowSleep) { float minSleepTime = Float.MAX_VALUE; final float linTolSqr = Settings.linearSleepTolerance * Settings.linearSleepTolerance; final float angTolSqr = Settings.angularSleepTolerance * Settings.angularSleepTolerance; for (int i = 0; i < m_bodyCount; ++i) { Body b = m_bodies[i]; if (b.getType() == BodyType.STATIC) { continue; } if ((b.m_flags & Body.e_autoSleepFlag) == 0 || b.m_angularVelocity * b.m_angularVelocity > angTolSqr || Vec2.dot(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr) { b.m_sleepTime = 0.0f; minSleepTime = 0.0f; } else { b.m_sleepTime += h; minSleepTime = MathUtils.min(minSleepTime, b.m_sleepTime); } } if (minSleepTime >= Settings.timeToSleep && positionSolved) { for (int i = 0; i < m_bodyCount; ++i) { Body b = m_bodies[i]; b.setAwake(false); } } } } private final ContactSolver toiContactSolver = new ContactSolver(); private final ContactSolverDef toiSolverDef = new ContactSolverDef(); public void solveTOI (TimeStep subStep, int toiIndexA, int toiIndexB) { assert (toiIndexA < m_bodyCount); assert (toiIndexB < m_bodyCount); // Initialize the body state. for (int i = 0; i < m_bodyCount; ++i) { m_positions[i].c.x = m_bodies[i].m_sweep.c.x; m_positions[i].c.y = m_bodies[i].m_sweep.c.y; m_positions[i].a = m_bodies[i].m_sweep.a; m_velocities[i].v.x = m_bodies[i].m_linearVelocity.x; m_velocities[i].v.y = m_bodies[i].m_linearVelocity.y; m_velocities[i].w = m_bodies[i].m_angularVelocity; } toiSolverDef.contacts = m_contacts; toiSolverDef.count = m_contactCount; toiSolverDef.step = subStep; toiSolverDef.positions = m_positions; toiSolverDef.velocities = m_velocities; toiContactSolver.init(toiSolverDef); // Solve position constraints. for (int i = 0; i < subStep.positionIterations; ++i) { boolean contactsOkay = toiContactSolver.solveTOIPositionConstraints(toiIndexA, toiIndexB); if (contactsOkay) { break; } } // #if 0 // // Is the new position really safe? // for (int i = 0; i < m_contactCount; ++i) // { // Contact* c = m_contacts[i]; // Fixture* fA = c.GetFixtureA(); // Fixture* fB = c.GetFixtureB(); // // Body bA = fA.GetBody(); // Body bB = fB.GetBody(); // // int indexA = c.GetChildIndexA(); // int indexB = c.GetChildIndexB(); // // DistanceInput input; // input.proxyA.Set(fA.GetShape(), indexA); // input.proxyB.Set(fB.GetShape(), indexB); // input.transformA = bA.GetTransform(); // input.transformB = bB.GetTransform(); // input.useRadii = false; // // DistanceOutput output; // SimplexCache cache; // cache.count = 0; // Distance(&output, &cache, &input); // // if (output.distance == 0 || cache.count == 3) // { // cache.count += 0; // } // } // #endif // Leap of faith to new safe state. m_bodies[toiIndexA].m_sweep.c0.x = m_positions[toiIndexA].c.x; m_bodies[toiIndexA].m_sweep.c0.y = m_positions[toiIndexA].c.y; m_bodies[toiIndexA].m_sweep.a0 = m_positions[toiIndexA].a; m_bodies[toiIndexB].m_sweep.c0.set(m_positions[toiIndexB].c); m_bodies[toiIndexB].m_sweep.a0 = m_positions[toiIndexB].a; // No warm starting is needed for TOI events because warm // starting impulses were applied in the discrete solver. toiContactSolver.initializeVelocityConstraints(); // Solve velocity constraints. for (int i = 0; i < subStep.velocityIterations; ++i) { toiContactSolver.solveVelocityConstraints(); } // Don't store the TOI contact forces for warm starting // because they can be quite large. float h = subStep.dt; // Integrate positions for (int i = 0; i < m_bodyCount; ++i) { Vec2 c = m_positions[i].c; float a = m_positions[i].a; Vec2 v = m_velocities[i].v; float w = m_velocities[i].w; // Check for large velocities float translationx = v.x * h; float translationy = v.y * h; if (translationx * translationx + translationy * translationy > Settings.maxTranslationSquared) { float ratio = Settings.maxTranslation / MathUtils.sqrt(translationx * translationx + translationy * translationy); v.mulLocal(ratio); } float rotation = h * w; if (rotation * rotation > Settings.maxRotationSquared) { float ratio = Settings.maxRotation / MathUtils.abs(rotation); w *= ratio; } // Integrate c.x += v.x * h; c.y += v.y * h; a += h * w; m_positions[i].c.x = c.x; m_positions[i].c.y = c.y; m_positions[i].a = a; m_velocities[i].v.x = v.x; m_velocities[i].v.y = v.y; m_velocities[i].w = w; // Sync bodies Body body = m_bodies[i]; body.m_sweep.c.x = c.x; body.m_sweep.c.y = c.y; body.m_sweep.a = a; body.m_linearVelocity.x = v.x; body.m_linearVelocity.y = v.y; body.m_angularVelocity = w; body.synchronizeTransform(); } report(toiContactSolver.m_velocityConstraints); } public void add (Body body) { assert (m_bodyCount < m_bodyCapacity); body.m_islandIndex = m_bodyCount; m_bodies[m_bodyCount] = body; ++m_bodyCount; } public void add (Contact contact) { assert (m_contactCount < m_contactCapacity); m_contacts[m_contactCount++] = contact; } public void add (Joint joint) { assert (m_jointCount < m_ [MASK] ); m_joints[m_jointCount++] = joint; } private final ContactImpulse impulse = new ContactImpulse(); public void report (ContactVelocityConstraint[] constraints) { if (m_listener == null) { return; } for (int i = 0; i < m_contactCount; ++i) { Contact c = m_contacts[i]; ContactVelocityConstraint vc = constraints[i]; impulse.count = vc.pointCount; for (int j = 0; j < vc.pointCount; ++j) { impulse.normalImpulses[j] = vc.points[j].normalImpulse; impulse.tangentImpulses[j] = vc.points[j].tangentImpulse; } m_listener.postSolve(c, impulse); } } } ","jointCapacity " "/* * Copyright 2015 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.realm.entities; import org.bson.types.Decimal128; import org.bson.types.ObjectId; import java.math.BigDecimal; import java.util.Date; import java.util.UUID; import io.realm.RealmList; import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.TestHelper; import io.realm.annotations.LinkingObjects; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; // Always follow below order and put comments like below to make NullTypes Related cases // 1 String // 2 Bytes // 3 Boolean // 4 Byte // 5 Short // 6 Integer // 7 Long // 8 Float // 9 Double // 10 Date // 11 Object public class NullTypes extends RealmObject { public static final String CLASS_NAME = ""NullTypes""; public static final String FIELD_ID = ""id""; public static final String FIELD_STRING_NOT_NULL = ""fieldStringNotNull""; public static final String FIELD_STRING_NULL = ""fieldStringNull""; public static final String FIELD_BYTES_NOT_NULL = ""fieldBytesNotNull""; public static final String FIELD_BYTES_NULL = ""fieldBytesNull""; public static final String FIELD_BOOLEAN_NOT_NULL = ""fieldBooleanNotNull""; public static final String FIELD_BOOLEAN_NULL = ""fieldBooleanNull""; public static final String FIELD_BYTE_NOT_NULL = ""fieldByteNotNull""; public static final String FIELD_BYTE_NULL = ""fieldByteNull""; public static final String FIELD_SHORT_NOT_NULL = ""fieldShortNotNull""; public static final String FIELD_SHORT_NULL = ""fieldShortNull""; public static final String FIELD_INTEGER_NOT_NULL = ""fieldIntegerNotNull""; public static final String FIELD_INTEGER_NULL = ""fieldIntegerNull""; public static final String FIELD_LONG_NOT_NULL = ""fieldLongNotNull""; public static final String FIELD_LONG_NULL = ""fieldLongNull""; public static final String FIELD_FLOAT_NOT_NULL = ""fieldFloatNotNull""; public static final String FIELD_FLOAT_NULL = ""fieldFloatNull""; public static final String FIELD_DOUBLE_NOT_NULL = ""fieldDoubleNotNull""; public static final String FIELD_DOUBLE_NULL = ""fieldDoubleNull""; public static final String FIELD_DATE_NOT_NULL = ""fieldDateNotNull""; public static final String FIELD_DATE_NULL = ""fieldDateNull""; public static final String FIELD_DECIMAL128_NULL = ""fieldDecimal128Null""; public static final String FIELD_DECIMAL128_NOT_NULL = ""fieldDecimal128NotNull""; public static final String FIELD_OBJECT_ID_NULL = ""fieldObjectIdNull""; public static final String FIELD_OBJECT_ID_NOT_NULL = ""fieldObjectIdNotNull""; public static final String FIELD_UUID_NULL = ""fieldUUIDNull""; public static final String FIELD_UUID_NOT_NULL = ""fieldUUIDNotNull""; public static final String FIELD_OBJECT_NULL = ""fieldObjectNull""; public static final String FIELD_LIST_NULL = ""fieldListNull""; public static final String FIELD_LO_OBJECT = ""objectParents""; public static final String FIELD_LO_LIST = ""listParents""; public static final String FIELD_STRING_LIST_NOT_NULL = "" [MASK] ""; public static final String FIELD_STRING_LIST_NULL = ""fieldStringListNull""; public static final String FIELD_BINARY_LIST_NOT_NULL = ""fieldBinaryListNotNull""; public static final String FIELD_BINARY_LIST_NULL = ""fieldBinaryListNull""; public static final String FIELD_BOOLEAN_LIST_NOT_NULL = ""fieldBooleanListNotNull""; public static final String FIELD_BOOLEAN_LIST_NULL = ""fieldBooleanListNull""; public static final String FIELD_LONG_LIST_NOT_NULL = ""fieldLongListNotNull""; public static final String FIELD_LONG_LIST_NULL = ""fieldLongListNull""; public static final String FIELD_INTEGER_LIST_NOT_NULL = ""fieldIntegerListNotNull""; public static final String FIELD_INTEGER_LIST_NULL = ""fieldIntegerListNull""; public static final String FIELD_SHORT_LIST_NOT_NULL = ""fieldShortListNotNull""; public static final String FIELD_SHORT_LIST_NULL = ""fieldShortListNull""; public static final String FIELD_BYTE_LIST_NOT_NULL = ""fieldByteListNotNull""; public static final String FIELD_BYTE_LIST_NULL = ""fieldByteListNull""; public static final String FIELD_DOUBLE_LIST_NOT_NULL = ""fieldDoubleListNotNull""; public static final String FIELD_DOUBLE_LIST_NULL = ""fieldDoubleListNull""; public static final String FIELD_FLOAT_LIST_NOT_NULL = ""fieldFloatListNotNull""; public static final String FIELD_FLOAT_LIST_NULL = ""fieldFloatListNull""; public static final String FIELD_DATE_LIST_NOT_NULL = ""fieldDateListNotNull""; public static final String FIELD_DATE_LIST_NULL = ""fieldDateListNull""; public static final String FIELD_DECIMAL128_LIST_NULL = ""fieldDecimal128ListNull""; public static final String FIELD_DECIMAL128_LIST_NOT_NULL = ""fieldDecimal128ListNotNull""; public static final String FIELD_OBJECT_ID_LIST_NULL = ""fieldObjectIdListNull""; public static final String FIELD_OBJECT_ID_LIST_NOT_NULL = ""fieldObjectIdListNotNull""; public static final String FIELD_UUID_LIST_NULL = ""fieldUUIDListNull""; public static final String FIELD_UUID_LIST_NOT_NULL = ""fieldUUIDListNotNull""; @PrimaryKey private int id; @Required private String fieldStringNotNull = """"; private String fieldStringNull; @Required private byte[] fieldBytesNotNull = new byte[0]; private byte[] fieldBytesNull; @Required private Boolean fieldBooleanNotNull = false; private Boolean fieldBooleanNull; @Required private Byte fieldByteNotNull = 0; private Byte fieldByteNull; @Required private Short fieldShortNotNull = 0; private Short fieldShortNull; @Required private Integer fieldIntegerNotNull = 0; private Integer fieldIntegerNull; @Required private Long fieldLongNotNull = 0L; private Long fieldLongNull; @Required private Float fieldFloatNotNull = 0F; private Float fieldFloatNull; @Required private Double fieldDoubleNotNull = 0D; private Double fieldDoubleNull; @Required private Date fieldDateNotNull = new Date(0); private Date fieldDateNull; @Required private Decimal128 fieldDecimal128NotNull = new Decimal128(BigDecimal.ZERO); private Decimal128 fieldDecimal128Null; @Required private ObjectId fieldObjectIdNotNull = new ObjectId(TestHelper.generateObjectIdHexString(0)); private ObjectId fieldObjectIdNull; @Required private UUID fieldUUIDNotNull = UUID.randomUUID(); private UUID fieldUUIDNull; private NullTypes fieldObjectNull; // never nullable private RealmList fieldListNull; @Required private RealmList [MASK] ; private RealmList fieldStringListNull; @Required private RealmList fieldBinaryListNotNull; private RealmList fieldBinaryListNull; @Required private RealmList fieldBooleanListNotNull; private RealmList fieldBooleanListNull; @Required private RealmList fieldLongListNotNull; private RealmList fieldLongListNull; @Required private RealmList fieldIntegerListNotNull; private RealmList fieldIntegerListNull; @Required private RealmList fieldShortListNotNull; private RealmList fieldShortListNull; @Required private RealmList fieldByteListNotNull; private RealmList fieldByteListNull; @Required private RealmList fieldDoubleListNotNull; private RealmList fieldDoubleListNull; @Required private RealmList fieldFloatListNotNull; private RealmList fieldFloatListNull; @Required private RealmList fieldDateListNotNull; private RealmList fieldDateListNull; @Required private RealmList fieldDecimal128ListNotNull; private RealmList fieldDecimal128ListNull; @Required private RealmList fieldObjectIdListNotNull; private RealmList fieldObjectIdListNull; @Required private RealmList fieldUUIDListNotNull; private RealmList fieldUUIDListNull; // never nullable @LinkingObjects(FIELD_OBJECT_NULL) private final RealmResults objectParents = null; // never nullable @LinkingObjects(FIELD_LIST_NULL) private final RealmResults listParents = null; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFieldStringNotNull() { return fieldStringNotNull; } public void setFieldStringNotNull(String fieldStringNotNull) { this.fieldStringNotNull = fieldStringNotNull; } public String getFieldStringNull() { return fieldStringNull; } public void setFieldStringNull(String fieldStringNull) { this.fieldStringNull = fieldStringNull; } public byte[] getFieldBytesNull() { return fieldBytesNull; } public void setFieldBytesNull(byte[] fieldBytesNull) { this.fieldBytesNull = fieldBytesNull; } public byte[] getFieldBytesNotNull() { return fieldBytesNotNull; } public void setFieldBytesNotNull(byte[] fieldBytesNotNull) { this.fieldBytesNotNull = fieldBytesNotNull; } public Boolean getFieldBooleanNotNull() { return fieldBooleanNotNull; } public void setFieldBooleanNotNull(Boolean fieldBooleanNotNull) { this.fieldBooleanNotNull = fieldBooleanNotNull; } public Boolean getFieldBooleanNull() { return fieldBooleanNull; } public void setFieldBooleanNull(Boolean fieldBooleanNull) { this.fieldBooleanNull = fieldBooleanNull; } public Byte getFieldByteNotNull() { return fieldByteNotNull; } public void setFieldByteNotNull(Byte fieldByteNotNull) { this.fieldByteNotNull = fieldByteNotNull; } public Byte getFieldByteNull() { return fieldByteNull; } public void setFieldByteNull(Byte fieldByteNull) { this.fieldByteNull = fieldByteNull; } public Short getFieldShortNotNull() { return fieldShortNotNull; } public void setFieldShortNotNull(Short fieldShortNotNull) { this.fieldShortNotNull = fieldShortNotNull; } public Short getFieldShortNull() { return fieldShortNull; } public void setFieldShortNull(Short fieldShortNull) { this.fieldShortNull = fieldShortNull; } public Integer getFieldIntegerNotNull() { return fieldIntegerNotNull; } public void setFieldIntegerNotNull(Integer fieldIntegerNotNull) { this.fieldIntegerNotNull = fieldIntegerNotNull; } public Integer getFieldIntegerNull() { return fieldIntegerNull; } public void setFieldIntegerNull(Integer fieldIntegerNull) { this.fieldIntegerNull = fieldIntegerNull; } public Long getFieldLongNotNull() { return fieldLongNotNull; } public void setFieldLongNotNull(Long fieldLongNotNull) { this.fieldLongNotNull = fieldLongNotNull; } public Long getFieldLongNull() { return fieldLongNull; } public void setFieldLongNull(Long fieldLongNull) { this.fieldLongNull = fieldLongNull; } public Float getFieldFloatNotNull() { return fieldFloatNotNull; } public void setFieldFloatNotNull(Float fieldFloatNotNull) { this.fieldFloatNotNull = fieldFloatNotNull; } public Float getFieldFloatNull() { return fieldFloatNull; } public void setFieldFloatNull(Float fieldFloatNull) { this.fieldFloatNull = fieldFloatNull; } public Double getFieldDoubleNotNull() { return fieldDoubleNotNull; } public void setFieldDoubleNotNull(Double fieldDoubleNotNull) { this.fieldDoubleNotNull = fieldDoubleNotNull; } public Double getFieldDoubleNull() { return fieldDoubleNull; } public void setFieldDoubleNull(Double fieldDoubleNull) { this.fieldDoubleNull = fieldDoubleNull; } public Date getFieldDateNotNull() { return fieldDateNotNull; } public void setFieldDateNotNull(Date fieldDateNotNull) { this.fieldDateNotNull = fieldDateNotNull; } public Date getFieldDateNull() { return fieldDateNull; } public void setFieldDateNull(Date fieldDateNull) { this.fieldDateNull = fieldDateNull; } public NullTypes getFieldObjectNull() { return fieldObjectNull; } public void setFieldObjectNull(NullTypes fieldObjectNull) { this.fieldObjectNull = fieldObjectNull; } public RealmList getFieldListNull() { return fieldListNull; } public void setFieldListNull(RealmList fieldListNull) { this.fieldListNull = fieldListNull; } public RealmResults getObjectParents() { return objectParents; } public RealmResults getListParents() { return listParents; } public RealmList getFieldStringListNotNull() { return [MASK] ; } public void setFieldStringListNotNull(RealmList [MASK] ) { this. [MASK] = [MASK] ; } public RealmList getFieldStringListNull() { return fieldStringListNull; } public void setFieldStringListNull(RealmList fieldStringListNull) { this.fieldStringListNull = fieldStringListNull; } public RealmList getFieldBinaryListNotNull() { return fieldBinaryListNotNull; } public void setFieldBinaryListNotNull(RealmList fieldBinaryListNotNull) { this.fieldBinaryListNotNull = fieldBinaryListNotNull; } public RealmList getFieldBinaryListNull() { return fieldBinaryListNull; } public void setFieldBinaryListNull(RealmList fieldBinaryListNull) { this.fieldBinaryListNull = fieldBinaryListNull; } public RealmList getFieldBooleanListNotNull() { return fieldBooleanListNotNull; } public void setFieldBooleanListNotNull(RealmList fieldBooleanListNotNull) { this.fieldBooleanListNotNull = fieldBooleanListNotNull; } public RealmList getFieldBooleanListNull() { return fieldBooleanListNull; } public void setFieldBooleanListNull(RealmList fieldBooleanListNull) { this.fieldBooleanListNull = fieldBooleanListNull; } public RealmList getFieldLongListNotNull() { return fieldLongListNotNull; } public void setFieldLongListNotNull(RealmList fieldLongListNotNull) { this.fieldLongListNotNull = fieldLongListNotNull; } public RealmList getFieldLongListNull() { return fieldLongListNull; } public void setFieldLongListNull(RealmList fieldLongListNull) { this.fieldLongListNull = fieldLongListNull; } public RealmList getFieldIntegerListNotNull() { return fieldIntegerListNotNull; } public void setFieldIntegerListNotNull(RealmList fieldIntegerListNotNull) { this.fieldIntegerListNotNull = fieldIntegerListNotNull; } public RealmList getFieldIntegerListNull() { return fieldIntegerListNull; } public void setFieldIntegerListNull(RealmList fieldIntegerListNull) { this.fieldIntegerListNull = fieldIntegerListNull; } public RealmList getFieldShortListNotNull() { return fieldShortListNotNull; } public void setFieldShortListNotNull(RealmList fieldShortListNotNull) { this.fieldShortListNotNull = fieldShortListNotNull; } public RealmList getFieldShortListNull() { return fieldShortListNull; } public void setFieldShortListNull(RealmList fieldShortListNull) { this.fieldShortListNull = fieldShortListNull; } public RealmList getFieldByteListNotNull() { return fieldByteListNotNull; } public void setFieldByteListNotNull(RealmList fieldByteListNotNull) { this.fieldByteListNotNull = fieldByteListNotNull; } public RealmList getFieldByteListNull() { return fieldByteListNull; } public void setFieldByteListNull(RealmList fieldByteListNull) { this.fieldByteListNull = fieldByteListNull; } public RealmList getFieldDoubleListNotNull() { return fieldDoubleListNotNull; } public void setFieldDoubleListNotNull(RealmList fieldDoubleListNotNull) { this.fieldDoubleListNotNull = fieldDoubleListNotNull; } public RealmList getFieldDoubleListNull() { return fieldDoubleListNull; } public void setFieldDoubleListNull(RealmList fieldDoubleListNull) { this.fieldDoubleListNull = fieldDoubleListNull; } public RealmList getFieldFloatListNotNull() { return fieldFloatListNotNull; } public void setFieldFloatListNotNull(RealmList fieldFloatListNotNull) { this.fieldFloatListNotNull = fieldFloatListNotNull; } public RealmList getFieldFloatListNull() { return fieldFloatListNull; } public void setFieldFloatListNull(RealmList fieldFloatListNull) { this.fieldFloatListNull = fieldFloatListNull; } public RealmList getFieldDateListNotNull() { return fieldDateListNotNull; } public void setFieldDateListNotNull(RealmList fieldDateListNotNull) { this.fieldDateListNotNull = fieldDateListNotNull; } public RealmList getFieldDateListNull() { return fieldDateListNull; } public void setFieldDateListNull(RealmList fieldDateListNull) { this.fieldDateListNull = fieldDateListNull; } public Decimal128 getFieldDecimal128NotNull() { return fieldDecimal128NotNull; } public void setFieldDecimal128NotNull(Decimal128 fieldDecimal128NotNull) { this.fieldDecimal128NotNull = fieldDecimal128NotNull; } public Decimal128 getFieldDecimal128Null() { return fieldDecimal128Null; } public void setFieldDecimal128Null(Decimal128 fieldDecimal128Null) { this.fieldDecimal128Null = fieldDecimal128Null; } public ObjectId getFieldObjectIdNotNull() { return fieldObjectIdNotNull; } public void setFieldObjectIdNotNull(ObjectId fieldObjectIdNotNull) { this.fieldObjectIdNotNull = fieldObjectIdNotNull; } public ObjectId getFieldObjectIdNull() { return fieldObjectIdNull; } public void setFieldObjectIdNull(ObjectId fieldObjectIdNull) { this.fieldObjectIdNull = fieldObjectIdNull; } public RealmList getFieldDecimal128ListNotNull() { return fieldDecimal128ListNotNull; } public void setFieldDecimal128ListNotNull(RealmList fieldDecimal128ListNotNull) { this.fieldDecimal128ListNotNull = fieldDecimal128ListNotNull; } public RealmList getFieldDecimal128ListNull() { return fieldDecimal128ListNull; } public void setFieldDecimal128ListNull(RealmList fieldDecimal128ListNull) { this.fieldDecimal128ListNull = fieldDecimal128ListNull; } public RealmList getFieldObjectIdListNotNull() { return fieldObjectIdListNotNull; } public void setFieldObjectIdListNotNull(RealmList fieldObjectIdListNotNull) { this.fieldObjectIdListNotNull = fieldObjectIdListNotNull; } public RealmList getFieldObjectIdListNull() { return fieldObjectIdListNull; } public void setFieldObjectIdListNull(RealmList fieldObjectIdListNull) { this.fieldObjectIdListNull = fieldObjectIdListNull; } public RealmList getFieldUUIDListNotNull() { return fieldUUIDListNotNull; } public void setFieldUUIDListNotNull(RealmList fieldUUIDListNotNull) { this.fieldUUIDListNotNull = fieldUUIDListNotNull; } public RealmList getFieldUUIDListNull() { return fieldUUIDListNull; } public void setFieldUUIDListNull(RealmList fieldUUIDListNull) { this.fieldUUIDListNull = fieldUUIDListNull; } public UUID getFieldUUIDNotNull() { return fieldUUIDNotNull; } public void setFieldUUIDNotNull(UUID fieldUUIDNotNull) { this.fieldUUIDNotNull = fieldUUIDNotNull; } public UUID getFieldUUIDNull() { return fieldUUIDNull; } public void setFieldUUIDNull(UUID fieldUUIDNull) { this.fieldUUIDNull = fieldUUIDNull; } } ","fieldStringListNotNull " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.classfile.attribute.visitor; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.annotation.*; import proguard.classfile.attribute.module.*; import proguard.classfile.attribute.preverification.*; /** * This interface specifies the methods for a visitor of Attribute * objects. * * @author Eric Lafortune */ public interface AttributeVisitor { // Attributes that are attached to classes. public void visitUnknownAttribute( Clazz clazz, UnknownAttribute unknownAttribute); public void visitBootstrapMethodsAttribute( Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute); public void visitSourceFileAttribute( Clazz clazz, SourceFileAttribute sourceFileAttribute); public void visitSourceDirAttribute( Clazz clazz, SourceDirAttribute sourceDirAttribute); public void visitInnerClassesAttribute( Clazz clazz, InnerClassesAttribute innerClassesAttribute); public void visitEnclosingMethodAttribute( Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute); public void visitNestHostAttribute( Clazz clazz, NestHostAttribute nestHostAttribute); public void visitNestMembersAttribute( Clazz clazz, NestMembersAttribute nestMembersAttribute); public void visitModuleAttribute( Clazz clazz, ModuleAttribute moduleAttribute); public void visitModuleMainClassAttribute( Clazz clazz, ModuleMainClassAttribute moduleMainClassAttribute); public void visitModulePackagesAttribute( Clazz clazz, ModulePackagesAttribute modulePackagesAttribute); public void visitDeprecatedAttribute( Clazz clazz, DeprecatedAttribute deprecatedAttribute); public void visitDeprecatedAttribute( Clazz clazz, Field field, DeprecatedAttribute deprecatedAttribute); public void visitDeprecatedAttribute( Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute); public void visitSyntheticAttribute( Clazz clazz, SyntheticAttribute syntheticAttribute); public void visitSyntheticAttribute( Clazz clazz, Field field, SyntheticAttribute syntheticAttribute); public void visitSyntheticAttribute( Clazz clazz, Method method, SyntheticAttribute syntheticAttribute); public void visitSignatureAttribute( Clazz clazz, SignatureAttribute signatureAttribute); public void visitSignatureAttribute( Clazz clazz, Field field, SignatureAttribute signatureAttribute); public void visitSignatureAttribute( Clazz clazz, Method method, SignatureAttribute signatureAttribute); // Attributes that are attached to fields. public void visitConstantValueAttribute( Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute); // Attributes that are attached to methods. public void visitMethodParametersAttribute( Clazz clazz, Method method, MethodParametersAttribute methodParametersAttribute); public void visitExceptionsAttribute( Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute); public void visitCodeAttribute( Clazz clazz, Method method, CodeAttribute [MASK] ); // Attributes that are attached to code attributes. public void visitStackMapAttribute( Clazz clazz, Method method, CodeAttribute [MASK] , StackMapAttribute stackMapAttribute); public void visitStackMapTableAttribute( Clazz clazz, Method method, CodeAttribute [MASK] , StackMapTableAttribute stackMapTableAttribute); public void visitLineNumberTableAttribute( Clazz clazz, Method method, CodeAttribute [MASK] , LineNumberTableAttribute lineNumberTableAttribute); public void visitLocalVariableTableAttribute( Clazz clazz, Method method, CodeAttribute [MASK] , LocalVariableTableAttribute localVariableTableAttribute); public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute [MASK] , LocalVariableTypeTableAttribute localVariableTypeTableAttribute); // Annotation attributes. public void visitRuntimeVisibleAnnotationsAttribute( Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute); public void visitRuntimeVisibleAnnotationsAttribute( Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute); public void visitRuntimeVisibleAnnotationsAttribute( Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute); public void visitRuntimeInvisibleAnnotationsAttribute( Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute); public void visitRuntimeInvisibleAnnotationsAttribute( Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute); public void visitRuntimeInvisibleAnnotationsAttribute( Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute); public void visitRuntimeVisibleParameterAnnotationsAttribute( Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute); public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute); public void visitRuntimeVisibleTypeAnnotationsAttribute( Clazz clazz, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute); public void visitRuntimeVisibleTypeAnnotationsAttribute( Clazz clazz, Field field, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute); public void visitRuntimeVisibleTypeAnnotationsAttribute( Clazz clazz, Method method, RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute); public void visitRuntimeVisibleTypeAnnotationsAttribute( Clazz clazz, Method method, CodeAttribute [MASK] , RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute); public void visitRuntimeInvisibleTypeAnnotationsAttribute( Clazz clazz, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute); public void visitRuntimeInvisibleTypeAnnotationsAttribute( Clazz clazz, Field field, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute); public void visitRuntimeInvisibleTypeAnnotationsAttribute( Clazz clazz, Method method, RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute); public void visitRuntimeInvisibleTypeAnnotationsAttribute( Clazz clazz, Method method, CodeAttribute [MASK] , RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute); public void visitAnnotationDefaultAttribute( Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute); }","codeAttribute " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.utils; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; import static com.badlogic.gdx.utils.ObjectSet. [MASK] ; /** An unordered map where the keys are objects and the values are unboxed floats. Null keys are not allowed. No allocation is * done except when growing the table size. *

* This class performs fast contains and remove (typically O(1), worst case O(n) but that is rare in practice). Add may be * slightly slower, depending on hash collisions. Hashcodes are rehashed to reduce collisions and the need to resize. Load factors * greater than 0.91 greatly increase the chances to resize to the next higher POT size. *

* Unordered sets and maps are not designed to provide especially fast iteration. Iteration is faster with OrderedSet and * OrderedMap. *

* This implementation uses linear probing with the backward shift algorithm for removal. Hashcodes are rehashed using Fibonacci * hashing, instead of the more common power-of-two mask, to better distribute poor hashCodes (see Malte * Skarupke's blog post). Linear probing continues to work even when all hashCodes collide, just more slowly. * @author Nathan Sweet * @author Tommy Ettinger */ public class ObjectFloatMap implements Iterable> { public int size; K[] keyTable; float[] valueTable; float loadFactor; int threshold; /** Used by {@link #place(Object)} to bit shift the upper bits of a {@code long} into a usable range (>= 0 and <= * {@link #mask}). The shift can be negative, which is convenient to match the number of bits in mask: if mask is a 7-bit * number, a shift of -7 shifts the upper 7 bits into the lowest 7 positions. This class sets the shift > 32 and < 64, * which if used with an int will still move the upper bits of an int to the lower bits due to Java's implicit modulus on * shifts. *

* {@link #mask} can also be used to mask the low bits of a number, which may be faster for some hashcodes, if * {@link #place(Object)} is overridden. */ protected int shift; /** A bitmask used to confine hashcodes to the size of the table. Must be all 1 bits in its low positions, ie a power of two * minus 1. If {@link #place(Object)} is overriden, this can be used instead of {@link #shift} to isolate usable bits of a * hash. */ protected int mask; transient Entries entries1, entries2; transient Values values1, values2; transient Keys keys1, keys2; /** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */ public ObjectFloatMap () { this(51, 0.8f); } /** Creates a new map with a load factor of 0.8. * @param initialCapacity The backing array size is initialCapacity / loadFactor, increased to the next power of two. */ public ObjectFloatMap (int initialCapacity) { this(initialCapacity, 0.8f); } /** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before * growing the backing table. * @param initialCapacity The backing array size is initialCapacity / loadFactor, increased to the next power of two. */ public ObjectFloatMap (int initialCapacity, float loadFactor) { if (loadFactor <= 0f || loadFactor >= 1f) throw new IllegalArgumentException(""loadFactor must be > 0 and < 1: "" + loadFactor); this.loadFactor = loadFactor; int [MASK] = [MASK] (initialCapacity, loadFactor); threshold = (int)( [MASK] * loadFactor); mask = [MASK] - 1; shift = Long.numberOfLeadingZeros(mask); keyTable = (K[])new Object[ [MASK] ]; valueTable = new float[ [MASK] ]; } /** Creates a new map identical to the specified map. */ public ObjectFloatMap (ObjectFloatMap map) { this((int)Math.floor(map.keyTable.length * map.loadFactor), map.loadFactor); System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length); System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length); size = map.size; } /** Returns an index >= 0 and <= {@link #mask} for the specified {@code item}. *

* The default implementation uses Fibonacci hashing on the item's {@link Object#hashCode()}: the hashcode is multiplied by a * long constant (2 to the 64th, divided by the golden ratio) then the uppermost bits are shifted into the lowest positions to * obtain an index in the desired range. Multiplication by a long may be slower than int (eg on GWT) but greatly improves * rehashing, allowing even very poor hashcodes, such as those that only differ in their upper bits, to be used without high * collision rates. Fibonacci hashing has increased collision rates when all or most hashcodes are multiples of larger * Fibonacci numbers (see Malte * Skarupke's blog post). *

* This method can be overriden to customizing hashing. This may be useful eg in the unlikely event that most hashcodes are * Fibonacci numbers, if keys provide poor or incorrect hashcodes, or to simplify hashing if keys provide high quality * hashcodes and don't need Fibonacci hashing: {@code return item.hashCode() & mask;} */ protected int place (K item) { return (int)(item.hashCode() * 0x9E3779B97F4A7C15L >>> shift); } /** Returns the index of the key if already present, else -(index + 1) for the next empty index. This can be overridden in this * pacakge to compare for equality differently than {@link Object#equals(Object)}. */ int locateKey (K key) { if (key == null) throw new IllegalArgumentException(""key cannot be null.""); K[] keyTable = this.keyTable; for (int i = place(key);; i = i + 1 & mask) { K other = keyTable[i]; if (other == null) return -(i + 1); // Empty space is available. if (other.equals(key)) return i; // Same key was found. } } public void put (K key, float value) { int i = locateKey(key); if (i >= 0) { // Existing key was found. valueTable[i] = value; return; } i = -(i + 1); // Empty space was found. keyTable[i] = key; valueTable[i] = value; if (++size >= threshold) resize(keyTable.length << 1); } /** Returns the old value associated with the specified key, or the specified default value. * @param defaultValue {@link Float#NaN} can be used for a value unlikely to be in the map. */ public float put (K key, float value, float defaultValue) { int i = locateKey(key); if (i >= 0) { // Existing key was found. float oldValue = valueTable[i]; valueTable[i] = value; return oldValue; } i = -(i + 1); // Empty space was found. keyTable[i] = key; valueTable[i] = value; if (++size >= threshold) resize(keyTable.length << 1); return defaultValue; } public void putAll (ObjectFloatMap map) { ensureCapacity(map.size); K[] keyTable = map.keyTable; float[] valueTable = map.valueTable; K key; for (int i = 0, n = keyTable.length; i < n; i++) { key = keyTable[i]; if (key != null) put(key, valueTable[i]); } } /** Skips checks for existing keys, doesn't increment size. */ private void putResize (K key, float value) { K[] keyTable = this.keyTable; for (int i = place(key);; i = (i + 1) & mask) { if (keyTable[i] == null) { keyTable[i] = key; valueTable[i] = value; return; } } } /** Returns the value for the specified key, or the default value if the key is not in the map. * @param defaultValue {@link Float#NaN} can be used for a value unlikely to be in the map. */ public float get (K key, float defaultValue) { int i = locateKey(key); return i < 0 ? defaultValue : valueTable[i]; } /** Returns the key's current value and increments the stored value. If the key is not in the map, defaultValue + increment is * put into the map and defaultValue is returned. */ public float getAndIncrement (K key, float defaultValue, float increment) { int i = locateKey(key); if (i >= 0) { // Existing key was found. float oldValue = valueTable[i]; valueTable[i] += increment; return oldValue; } i = -(i + 1); // Empty space was found. keyTable[i] = key; valueTable[i] = defaultValue + increment; if (++size >= threshold) resize(keyTable.length << 1); return defaultValue; } /** Returns the value for the removed key, or the default value if the key is not in the map. * @param defaultValue {@link Float#NaN} can be used for a value unlikely to be in the map. */ public float remove (K key, float defaultValue) { int i = locateKey(key); if (i < 0) return defaultValue; K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; float oldValue = valueTable[i]; int mask = this.mask, next = i + 1 & mask; while ((key = keyTable[next]) != null) { int placement = place(key); if ((next - placement & mask) > (i - placement & mask)) { keyTable[i] = key; valueTable[i] = valueTable[next]; i = next; } next = next + 1 & mask; } keyTable[i] = null; size--; return oldValue; } /** Returns true if the map has one or more items. */ public boolean notEmpty () { return size > 0; } /** Returns true if the map is empty. */ public boolean isEmpty () { return size == 0; } /** Reduces the size of the backing arrays to be the specified capacity / loadFactor, or less. If the capacity is already less, * nothing is done. If the map contains more items than the specified capacity, the next highest power of two capacity is used * instead. */ public void shrink (int maximumCapacity) { if (maximumCapacity < 0) throw new IllegalArgumentException(""maximumCapacity must be >= 0: "" + maximumCapacity); int [MASK] = [MASK] (maximumCapacity, loadFactor); if (keyTable.length > [MASK] ) resize( [MASK] ); } /** Clears the map and reduces the size of the backing arrays to be the specified capacity / loadFactor, if they are larger. */ public void clear (int maximumCapacity) { int [MASK] = [MASK] (maximumCapacity, loadFactor); if (keyTable.length <= [MASK] ) { clear(); return; } size = 0; resize( [MASK] ); } public void clear () { if (size == 0) return; size = 0; Arrays.fill(keyTable, null); } /** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may * be an expensive operation. */ public boolean containsValue (float value) { K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; for (int i = valueTable.length - 1; i >= 0; i--) if (keyTable[i] != null && valueTable[i] == value) return true; return false; } /** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may * be an expensive operation. */ public boolean containsValue (float value, float epsilon) { K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; for (int i = valueTable.length - 1; i >= 0; i--) if (keyTable[i] != null && Math.abs(valueTable[i] - value) <= epsilon) return true; return false; } public boolean containsKey (K key) { return locateKey(key) >= 0; } /** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares * every value, which may be an expensive operation. */ public @Null K findKey (float value) { K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; for (int i = valueTable.length - 1; i >= 0; i--) { K key = keyTable[i]; if (key != null && valueTable[i] == value) return key; } return null; } /** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares * every value, which may be an expensive operation. */ public @Null K findKey (float value, float epsilon) { K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; for (int i = valueTable.length - 1; i >= 0; i--) { K key = keyTable[i]; if (key != null && Math.abs(valueTable[i] - value) <= epsilon) return key; } return null; } /** Increases the size of the backing array to accommodate the specified number of additional items / loadFactor. Useful before * adding many items to avoid multiple backing array resizes. */ public void ensureCapacity (int additionalCapacity) { int [MASK] = [MASK] (size + additionalCapacity, loadFactor); if (keyTable.length < [MASK] ) resize( [MASK] ); } final void resize (int newSize) { int oldCapacity = keyTable.length; threshold = (int)(newSize * loadFactor); mask = newSize - 1; shift = Long.numberOfLeadingZeros(mask); K[] oldKeyTable = keyTable; float[] oldValueTable = valueTable; keyTable = (K[])new Object[newSize]; valueTable = new float[newSize]; if (size > 0) { for (int i = 0; i < oldCapacity; i++) { K key = oldKeyTable[i]; if (key != null) putResize(key, oldValueTable[i]); } } } public int hashCode () { int h = size; K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; for (int i = 0, n = keyTable.length; i < n; i++) { K key = keyTable[i]; if (key != null) h += key.hashCode() + NumberUtils.floatToRawIntBits(valueTable[i]); } return h; } public boolean equals (Object obj) { if (obj == this) return true; if (!(obj instanceof ObjectFloatMap)) return false; ObjectFloatMap other = (ObjectFloatMap)obj; if (other.size != size) return false; K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; for (int i = 0, n = keyTable.length; i < n; i++) { K key = keyTable[i]; if (key != null) { float otherValue = other.get(key, 0); if (otherValue == 0 && !other.containsKey(key)) return false; if (otherValue != valueTable[i]) return false; } } return true; } public String toString (String separator) { return toString(separator, false); } public String toString () { return toString("", "", true); } private String toString (String separator, boolean braces) { if (size == 0) return braces ? ""{}"" : """"; java.lang.StringBuilder buffer = new java.lang.StringBuilder(32); if (braces) buffer.append('{'); K[] keyTable = this.keyTable; float[] valueTable = this.valueTable; int i = keyTable.length; while (i-- > 0) { K key = keyTable[i]; if (key == null) continue; buffer.append(key); buffer.append('='); buffer.append(valueTable[i]); break; } while (i-- > 0) { K key = keyTable[i]; if (key == null) continue; buffer.append(separator); buffer.append(key); buffer.append('='); buffer.append(valueTable[i]); } if (braces) buffer.append('}'); return buffer.toString(); } public Entries iterator () { return entries(); } /** Returns an iterator for the entries in the map. Remove is supported. *

* If {@link Collections#allocateIterators} is false, the same iterator instance is returned each time this method is called. * Use the {@link Entries} constructor for nested or multithreaded iteration. */ public Entries entries () { if (Collections.allocateIterators) return new Entries(this); if (entries1 == null) { entries1 = new Entries(this); entries2 = new Entries(this); } if (!entries1.valid) { entries1.reset(); entries1.valid = true; entries2.valid = false; return entries1; } entries2.reset(); entries2.valid = true; entries1.valid = false; return entries2; } /** Returns an iterator for the values in the map. Remove is supported. *

* If {@link Collections#allocateIterators} is false, the same iterator instance is returned each time this method is called. * Use the {@link Values} constructor for nested or multithreaded iteration. */ public Values values () { if (Collections.allocateIterators) return new Values(this); if (values1 == null) { values1 = new Values(this); values2 = new Values(this); } if (!values1.valid) { values1.reset(); values1.valid = true; values2.valid = false; return values1; } values2.reset(); values2.valid = true; values1.valid = false; return values2; } /** Returns an iterator for the keys in the map. Remove is supported. *

* If {@link Collections#allocateIterators} is false, the same iterator instance is returned each time this method is called. * Use the {@link Keys} constructor for nested or multithreaded iteration. */ public Keys keys () { if (Collections.allocateIterators) return new Keys(this); if (keys1 == null) { keys1 = new Keys(this); keys2 = new Keys(this); } if (!keys1.valid) { keys1.reset(); keys1.valid = true; keys2.valid = false; return keys1; } keys2.reset(); keys2.valid = true; keys1.valid = false; return keys2; } static public class Entry { public K key; public float value; public String toString () { return key + ""="" + value; } } static private class MapIterator { public boolean hasNext; final ObjectFloatMap map; int nextIndex, currentIndex; boolean valid = true; public MapIterator (ObjectFloatMap map) { this.map = map; reset(); } public void reset () { currentIndex = -1; nextIndex = -1; findNextIndex(); } void findNextIndex () { K[] keyTable = map.keyTable; for (int n = keyTable.length; ++nextIndex < n;) { if (keyTable[nextIndex] != null) { hasNext = true; return; } } hasNext = false; } public void remove () { int i = currentIndex; if (i < 0) throw new IllegalStateException(""next must be called before remove.""); K[] keyTable = map.keyTable; float[] valueTable = map.valueTable; int mask = map.mask, next = i + 1 & mask; K key; while ((key = keyTable[next]) != null) { int placement = map.place(key); if ((next - placement & mask) > (i - placement & mask)) { keyTable[i] = key; valueTable[i] = valueTable[next]; i = next; } next = next + 1 & mask; } keyTable[i] = null; map.size--; if (i != currentIndex) --nextIndex; currentIndex = -1; } } static public class Entries extends MapIterator implements Iterable>, Iterator> { Entry entry = new Entry(); public Entries (ObjectFloatMap map) { super(map); } /** Note the same entry instance is returned each time this method is called. */ public Entry next () { if (!hasNext) throw new NoSuchElementException(); if (!valid) throw new GdxRuntimeException(""#iterator() cannot be used nested.""); K[] keyTable = map.keyTable; entry.key = keyTable[nextIndex]; entry.value = map.valueTable[nextIndex]; currentIndex = nextIndex; findNextIndex(); return entry; } public boolean hasNext () { if (!valid) throw new GdxRuntimeException(""#iterator() cannot be used nested.""); return hasNext; } public Entries iterator () { return this; } } static public class Values extends MapIterator { public Values (ObjectFloatMap map) { super((ObjectFloatMap)map); } public boolean hasNext () { if (!valid) throw new GdxRuntimeException(""#iterator() cannot be used nested.""); return hasNext; } public float next () { if (!hasNext) throw new NoSuchElementException(); if (!valid) throw new GdxRuntimeException(""#iterator() cannot be used nested.""); float value = map.valueTable[nextIndex]; currentIndex = nextIndex; findNextIndex(); return value; } public Values iterator () { return this; } /** Returns a new array containing the remaining values. */ public FloatArray toArray () { FloatArray array = new FloatArray(true, map.size); while (hasNext) array.add(next()); return array; } /** Adds the remaining values to the specified array. */ public FloatArray toArray (FloatArray array) { while (hasNext) array.add(next()); return array; } } static public class Keys extends MapIterator implements Iterable, Iterator { public Keys (ObjectFloatMap map) { super(map); } public boolean hasNext () { if (!valid) throw new GdxRuntimeException(""#iterator() cannot be used nested.""); return hasNext; } public K next () { if (!hasNext) throw new NoSuchElementException(); if (!valid) throw new GdxRuntimeException(""#iterator() cannot be used nested.""); K key = map.keyTable[nextIndex]; currentIndex = nextIndex; findNextIndex(); return key; } public Keys iterator () { return this; } /** Returns a new array containing the remaining keys. */ public Array toArray () { return toArray(new Array(true, map.size)); } /** Adds the remaining keys to the array. */ public Array toArray (Array array) { while (hasNext) array.add(next()); return array; } } } ","tableSize " "/* * Copyright 2015 The Netty Project * * The Netty Project licenses this file to you under the Apache License, version 2.0 (the * ""License""); you may not use this file except in compliance with the License. You may obtain a * copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an ""AS IS"" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package io.netty.handler.codec.http2; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPromise; import io.netty.microbench.channel.EmbeddedChannelWriteReleaseHandlerContext; import io.netty.microbench.util.AbstractMicrobenchmark; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Warmup; import java.util.concurrent.TimeUnit; import static io.netty.buffer.Unpooled.directBuffer; import static io.netty.buffer.Unpooled.unreleasableBuffer; import static io.netty.handler.codec.http2.Http2CodecUtil.DATA_FRAME_HEADER_LENGTH; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE; import static io.netty.handler.codec.http2.Http2CodecUtil.verifyPadding; import static io.netty.handler.codec.http2.Http2CodecUtil.writeFrameHeaderInternal; import static io.netty.handler.codec.http2.Http2FrameTypes.DATA; import static io.netty.util.internal.ObjectUtil.checkPositive; import static java.lang.Math.max; import static java.lang.Math.min; @Fork(1) @Warmup(iterations = 5) @Measurement(iterations = 5) @State(Scope.Benchmark) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class Http2FrameWriterDataBenchmark extends AbstractMicrobenchmark { @Param({ ""64"", ""1024"", ""4096"", ""16384"", ""1048576"", ""4194304"" }) public int payloadSize; @Param({ ""0"", ""100"", ""255"" }) public int padding; @Param({ ""true"", ""false"" }) public boolean pooled; private ByteBuf payload; private ChannelHandlerContext ctx; private Http2DataWriter writer; private Http2DataWriter oldWriter; @Setup(Level.Trial) public void setup() { writer = new DefaultHttp2FrameWriter(); oldWriter = new OldDefaultHttp2FrameWriter(); payload = pooled ? PooledByteBufAllocator.DEFAULT.buffer(payloadSize) : Unpooled.buffer(payloadSize); payload.writeZero(payloadSize); ctx = new EmbeddedChannelWriteReleaseHandlerContext( pooled ? PooledByteBufAllocator.DEFAULT : UnpooledByteBufAllocator.DEFAULT, new ChannelInboundHandlerAdapter()) { @Override protected void handleException(Throwable t) { handleUnexpectedException(t); } }; } @TearDown(Level.Trial) public void teardown() throws Exception { if (payload != null) { payload.release(); } if (ctx != null) { ctx.close(); } } @Benchmark @BenchmarkMode(Mode.AverageTime) public void newWriter() { writer.writeData(ctx, 3, payload.retain(), padding, true, ctx.voidPromise()); ctx.flush(); } @Benchmark @BenchmarkMode(Mode.AverageTime) public void oldWriter() { oldWriter.writeData(ctx, 3, payload.retain(), padding, true, ctx.voidPromise()); ctx.flush(); } private static final class OldDefaultHttp2FrameWriter implements Http2DataWriter { private static final ByteBuf ZERO_BUFFER = unreleasableBuffer(directBuffer(MAX_UNSIGNED_BYTE).writeZero(MAX_UNSIGNED_BYTE)).asReadOnly(); private final int maxFrameSize = DEFAULT_MAX_FRAME_SIZE; @Override public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endStream, ChannelPromise promise) { final Http2CodecUtil.SimpleChannelPromiseAggregator [MASK] = new Http2CodecUtil.SimpleChannelPromiseAggregator(promise, ctx.channel(), ctx.executor()); final DataFrameHeader header = new DataFrameHeader(ctx, streamId); boolean needToReleaseHeaders = true; boolean needToReleaseData = true; try { checkPositive(streamId, ""streamId""); verifyPadding(padding); boolean lastFrame; int remainingData = data.readableBytes(); do { // Determine how much data and padding to write in this frame. Put all padding at the end. int frameDataBytes = min(remainingData, maxFrameSize); int framePaddingBytes = min(padding, max(0, (maxFrameSize - 1) - frameDataBytes)); // Decrement the remaining counters. padding -= framePaddingBytes; remainingData -= frameDataBytes; // Determine whether or not this is the last frame to be sent. lastFrame = remainingData == 0 && padding == 0; // Only the last frame is not retained. Until then, the outer finally must release. ByteBuf frameHeader = header.slice(frameDataBytes, framePaddingBytes, lastFrame && endStream); needToReleaseHeaders = !lastFrame; ctx.write(lastFrame ? frameHeader : frameHeader.retain(), [MASK] .newPromise()); // Write the frame data. ByteBuf frameData = data.readSlice(frameDataBytes); // Only the last frame is not retained. Until then, the outer finally must release. needToReleaseData = !lastFrame; ctx.write(lastFrame ? frameData : frameData.retain(), [MASK] .newPromise()); // Write the frame padding. if (paddingBytes(framePaddingBytes) > 0) { ctx.write(ZERO_BUFFER.slice(0, paddingBytes(framePaddingBytes)), [MASK] .newPromise()); } } while (!lastFrame); } catch (Throwable t) { try { if (needToReleaseHeaders) { header.release(); } if (needToReleaseData) { data.release(); } } finally { [MASK] .setFailure(t); [MASK] .doneAllocatingPromises(); } return [MASK] ; } return [MASK] .doneAllocatingPromises(); } private static int paddingBytes(int padding) { // The padding parameter contains the 1 byte pad length field as well as the trailing padding bytes. // Subtract 1, so to only get the number of padding bytes that need to be appended to the end of a frame. return padding - 1; } private static void writePaddingLength(ByteBuf buf, int padding) { if (padding > 0) { // It is assumed that the padding length has been bounds checked before this // Minus 1, as the pad length field is included in the padding parameter and is 1 byte wide. buf.writeByte(padding - 1); } } /** * Utility class that manages the creation of frame header buffers for {@code DATA} frames. Attempts * to reuse the same buffer repeatedly when splitting data into multiple frames. */ private static final class DataFrameHeader { private final int streamId; private final ByteBuf buffer; private final Http2Flags flags = new Http2Flags(); private int prevData; private int prevPadding; private ByteBuf frameHeader; DataFrameHeader(ChannelHandlerContext ctx, int streamId) { // All padding will be put at the end, so in the worst case we need 3 headers: // a repeated no-padding frame of maxFrameSize, a frame that has part data and part // padding, and a frame that has the remainder of the padding. buffer = ctx.alloc().buffer(3 * DATA_FRAME_HEADER_LENGTH); this.streamId = streamId; } /** * Gets the frame header buffer configured for the current frame. */ ByteBuf slice(int data, int padding, boolean endOfStream) { // Since we're reusing the current frame header whenever possible, check if anything changed // that requires a new header. if (data != prevData || padding != prevPadding || endOfStream != flags.endOfStream() || frameHeader == null) { // Update the header state. prevData = data; prevPadding = padding; flags.paddingPresent(padding > 0); flags.endOfStream(endOfStream); frameHeader = buffer.slice(buffer.readerIndex(), DATA_FRAME_HEADER_LENGTH).writerIndex(0); buffer.setIndex(buffer.readerIndex() + DATA_FRAME_HEADER_LENGTH, buffer.writerIndex() + DATA_FRAME_HEADER_LENGTH); int payloadLength = data + padding; writeFrameHeaderInternal(frameHeader, payloadLength, DATA, flags, streamId); writePaddingLength(frameHeader, padding); } return frameHeader.slice(); } void release() { buffer.release(); } } } } ","promiseAggregator " "/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: XSLTElementProcessor.java 469688 2006-10-31 22:39:43Z minchau $ */ package org.apache.xalan.processor; import java.util.ArrayList; import java.util.List; import java.util.Vector; import javax.xml.transform.TransformerException; import org.apache.xalan.res.XSLMessages; import org.apache.xalan.res.XSLTErrorResources; import org.apache.xalan.templates.ElemTemplateElement; import org.apache.xml.utils.IntStack; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.helpers.AttributesImpl; /** * This class acts as the superclass for all stylesheet element * processors, and deals with things that are common to all elements. * @see XSLT DTD */ public class XSLTElementProcessor extends ElemTemplateElement { static final long serialVersionUID = 5597421564955304421L; /** * Construct a processor for top-level elements. * @see XSLT DTD */ XSLTElementProcessor(){} private IntStack m_savedLastOrder; /** * The element definition that this processor conforms to. */ private XSLTElementDef m_elemDef; /** * Get the element definition that belongs to this element. * * @return The element definition object that produced and constrains this element. */ XSLTElementDef getElemDef() { return m_elemDef; } /** * Set the element definition that belongs to this element. * * @param def The element definition object that produced and constrains this element. */ void setElemDef(XSLTElementDef def) { m_elemDef = def; } /** * Resolve an external entity. * * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param publicId The public identifer, or null if none is * available. * @param systemId The system identifier provided in the XML * document. * @return The new input source, or null to require the * default behaviour. */ public InputSource resolveEntity( StylesheetHandler handler, String publicId, String systemId) throws org.xml.sax.SAXException { return null; } /** * Receive notification of a notation declaration. * * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param name The notation name. * @param publicId The notation public identifier, or null if not * available. * @param systemId The notation system identifier. * @see org.xml.sax.DTDHandler#notationDecl */ public void notationDecl(StylesheetHandler handler, String name, String publicId, String systemId) { // no op } /** * Receive notification of an unparsed entity declaration. * * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param name The entity name. * @param publicId The entity public identifier, or null if not * available. * @param systemId The entity system identifier. * @param notationName The name of the associated notation. * @see org.xml.sax.DTDHandler#unparsedEntityDecl */ public void unparsedEntityDecl(StylesheetHandler handler, String name, String publicId, String systemId, String notationName) { // no op } /** * Receive notification of the start of the non-text event. This * is sent to the current processor when any non-text event occurs. * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. */ public void startNonText(StylesheetHandler handler) throws org.xml.sax.SAXException { // no op } /** * Receive notification of the start of an element. * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param uri The Namespace URI, or an empty string. * @param localName The local name (without prefix), or empty string if not namespace processing. * @param rawName The qualified name (with prefix). * @param attributes The specified or defaulted attributes. */ public void startElement( StylesheetHandler handler, String uri, String localName, String rawName, Attributes attributes) throws org.xml.sax.SAXException { if (m_savedLastOrder == null) m_savedLastOrder = new IntStack(); m_savedLastOrder.push(getElemDef().getLastOrder()); getElemDef().setLastOrder(-1); } /** * Receive notification of the end of an element. * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param uri The Namespace URI, or an empty string. * @param localName The local name (without prefix), or empty string if not namespace processing. * @param rawName The qualified name (with prefix). */ public void endElement( StylesheetHandler handler, String uri, String localName, String rawName) throws org.xml.sax.SAXException { if (m_savedLastOrder != null && !m_savedLastOrder.empty()) getElemDef().setLastOrder(m_savedLastOrder.pop()); if (!getElemDef().getRequiredFound()) handler.error(XSLTErrorResources.ER_REQUIRED_ELEM_NOT_FOUND, new Object[]{getElemDef().getRequiredElem()}, null); } /** * Receive notification of character data inside an element. * * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param ch The characters. * @param start The start position in the character array. * @param length The number of characters to use from the * character array. */ public void characters( StylesheetHandler handler, char ch[], int start, int length) throws org.xml.sax.SAXException { handler.error(XSLTErrorResources.ER_CHARS_NOT_ALLOWED, null, null);//""Characters are not allowed at this point in the document!"", //null); } /** * Receive notification of ignorable whitespace in element content. * * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param ch The whitespace characters. * @param start The start position in the character array. * @param length The number of characters to use from the * character array. */ public void ignorableWhitespace( StylesheetHandler handler, char ch[], int start, int length) throws org.xml.sax.SAXException { // no op } /** * Receive notification of a processing instruction. * * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param target The processing instruction target. * @param data The processing instruction data, or null if * none is supplied. */ public void processingInstruction( StylesheetHandler handler, String target, String data) throws org.xml.sax.SAXException { // no op } /** * Receive notification of a skipped entity. * * * @param handler non-null reference to current StylesheetHandler that is constructing the Templates. * @param name The name of the skipped entity. */ public void skippedEntity(StylesheetHandler handler, String name) throws org.xml.sax.SAXException { // no op } /** * Set the properties of an object from the given attribute list. * @param handler The stylesheet's Content handler, needed for * error reporting. * @param rawName The raw name of the owner element, needed for * error reporting. * @param attributes The list of attributes. * @param target The target element where the properties will be set. */ void setPropertiesFromAttributes( StylesheetHandler handler, String rawName, Attributes attributes, ElemTemplateElement target) throws org.xml.sax.SAXException { setPropertiesFromAttributes(handler, rawName, attributes, target, true); } /** * Set the properties of an object from the given attribute list. * @param handler The stylesheet's Content handler, needed for * error reporting. * @param rawName The raw name of the owner element, needed for * error reporting. * @param attributes The list of attributes. * @param target The target element where the properties will be set. * @param throwError True if it should throw an error if an * attribute is not defined. * @return the attributes not allowed on this element. * * @throws TransformerException */ Attributes setPropertiesFromAttributes( StylesheetHandler handler, String rawName, Attributes attributes, ElemTemplateElement target, boolean throwError) throws org.xml.sax.SAXException { XSLTElementDef def = getElemDef(); AttributesImpl [MASK] = null; boolean isCompatibleMode = ((null != handler.getStylesheet() && handler.getStylesheet().getCompatibleMode()) || !throwError); if (isCompatibleMode) [MASK] = new AttributesImpl(); // Keep track of which XSLTAttributeDefs have been processed, so // I can see which default values need to be set. List processedDefs = new ArrayList(); // Keep track of XSLTAttributeDefs that were invalid List errorDefs = new ArrayList(); int nAttrs = attributes.getLength(); for (int i = 0; i < nAttrs; i++) { String attrUri = attributes.getURI(i); // Hack for Crimson. -sb if((null != attrUri) && (attrUri.length() == 0) && (attributes.getQName(i).startsWith(""xmlns:"") || attributes.getQName(i).equals(""xmlns""))) { attrUri = org.apache.xalan.templates.Constants.S_XMLNAMESPACEURI; } String attrLocalName = attributes.getLocalName(i); XSLTAttributeDef attrDef = def.getAttributeDef(attrUri, attrLocalName); if (null == attrDef) { if (!isCompatibleMode) { // Then barf, because this element does not allow this attribute. handler.error(XSLTErrorResources.ER_ATTR_NOT_ALLOWED, new Object[]{attributes.getQName(i), rawName}, null);//""\""""+attributes.getQName(i)+""\"""" //+ "" attribute is not allowed on the "" + rawName // + "" element!"", null); } else { [MASK] .addAttribute(attrUri, attrLocalName, attributes.getQName(i), attributes.getType(i), attributes.getValue(i)); } } else { //handle secure processing if(handler.getStylesheetProcessor()==null) System.out.println(""stylesheet processor null""); if(attrDef.getName().compareTo(""*"")==0 && handler.getStylesheetProcessor().isSecureProcessing()) { //foreign attributes are not allowed in secure processing mode // Then barf, because this element does not allow this attribute. handler.error(XSLTErrorResources.ER_ATTR_NOT_ALLOWED, new Object[]{attributes.getQName(i), rawName}, null);//""\""""+attributes.getQName(i)+""\"""" //+ "" attribute is not allowed on the "" + rawName // + "" element!"", null); } else { boolean success = attrDef.setAttrValue(handler, attrUri, attrLocalName, attributes.getQName(i), attributes.getValue(i), target); // Now we only add the element if it passed a validation check if (success) processedDefs.add(attrDef); else errorDefs.add(attrDef); } } } XSLTAttributeDef[] attrDefs = def.getAttributes(); int nAttrDefs = attrDefs.length; for (int i = 0; i < nAttrDefs; i++) { XSLTAttributeDef attrDef = attrDefs[i]; String defVal = attrDef.getDefault(); if (null != defVal) { if (!processedDefs.contains(attrDef)) { attrDef.setDefAttrValue(handler, target); } } if (attrDef.getRequired()) { if ((!processedDefs.contains(attrDef)) && (!errorDefs.contains(attrDef))) handler.error( XSLMessages.createMessage( XSLTErrorResources.ER_REQUIRES_ATTRIB, new Object[]{ rawName, attrDef.getName() }), null); } } return [MASK] ; } } ","undefines " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.tests; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Colors; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFontCache; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.GlyphLayout.GlyphRun; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.ui.Window; import com.badlogic.gdx.tests.utils.GdxTest; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.viewport.ScreenViewport; public class BitmapFontTest extends GdxTest { private Stage stage; private SpriteBatch spriteBatch; private BitmapFont font; private ShapeRenderer renderer; private BitmapFont multiPageFont; private BitmapFont smallFont; private GlyphLayout layout; private Label label; @Override public void create () { spriteBatch = new SpriteBatch(); // font = new BitmapFont(Gdx.files.internal(""data/verdana39.fnt""), false); font = new BitmapFont(Gdx.files.internal(""data/lsans-32-pad.fnt""), false); smallFont = new BitmapFont(); // uses LSans 15, the default // font = new FreeTypeFontGenerator(Gdx.files.internal(""data/lsans.ttf"")).generateFont(new FreeTypeFontParameter()); font.getData().markupEnabled = true; font.getData().breakChars = new char[] {'-'}; multiPageFont = new BitmapFont(Gdx.files.internal(""data/multipagefont.fnt"")); // Add user defined color Colors.put(""PERU"", Color.valueOf(""CD853F"")); renderer = new ShapeRenderer(); renderer.setProjectionMatrix(spriteBatch.getProjectionMatrix()); stage = new Stage(new ScreenViewport()); Skin skin = new Skin(Gdx.files.internal(""data/uiskin.json"")); BitmapFont labelFont = skin.get(""default-font"", BitmapFont.class); labelFont.getData().markupEnabled = true; // Notice that the last [] has been deliberately added to test the effect of excessive pop operations. // They are silently ignored, as expected. label = new Label(""<<[BLUE]M[RED]u[YELLOW]l[GREEN]t[OLIVE]ic[]o[]l[]o[]r[]*[MAROON]Label[][] [Unknown Color]>>"", skin); label.setPosition(100, 200); stage.addActor(label); Window window = new Window(""[RED]Multicolor[GREEN] Title"", skin); window.setPosition(400, 300); window.pack(); stage.addActor(window); layout = new GlyphLayout(); } @Override public void render () { // red.a = (red.a + Gdx.graphics.getDeltaTime() * 0.1f) % 1; int viewHeight = Gdx.graphics.getHeight(); ScreenUtils.clear(0, 0, 0, 1); // Test wrapping or truncation with the font directly. if (true) { // BitmapFont font = label.getStyle().font; BitmapFont font = this.font; font.getData().markupEnabled = true; font.getRegion().getTexture().setFilter(TextureFilter.Nearest, TextureFilter.Nearest); font.getData().setScale(2f); renderer.begin(ShapeRenderer.ShapeType.Line); renderer.setColor(0, 1, 0, 1); float w = Gdx.input.getX() - 10; // w = 855; renderer.rect(10, 10, w, 500); renderer.end(); spriteBatch.begin(); String text = ""your new""; // text = ""How quickly da[RED]ft jumping zebras vex.""; // text = ""Another font wrap is-sue, this time with multiple whitespace characters.""; // text = ""test with AGWlWi AGWlWi issue""; // text = ""AA BB \nEE""; // When wrapping after BB, there should not be a blank line before EE. text = ""[BLUE]A[]A BB [#00f000]EE[] T [GREEN]e[] \r\r[PINK]\n\nV[][YELLOW]a bb[] ([CYAN]5[]FFFurz)\nV[PURPLE]a[]\nVa\n[PURPLE]V[]a""; if (true) { // Test wrap. layout.setText(font, text, 0, text.length(), font.getColor(), w, Align.center, true, null); } else { // Test truncation. layout.setText(font, text, 0, text.length(), font.getColor(), w, Align.center, false, ""...""); } float meowy = (500 / 2 + layout.height / 2 + 5); font.draw(spriteBatch, layout, 10, 10 + meowy); spriteBatch.end(); Gdx.gl.glEnable(GL20.GL_BLEND); Gdx.gl.glBlendFunc(GL20.GL_ONE, GL20.GL_ONE); renderer.begin(ShapeRenderer.ShapeType.Line); float c = 0.8f; // GlyphLayout bounds if (true) { renderer.setColor(c, c, c, 1); renderer.rect(10 + 0.5f * (w - layout.width), 10 + meowy, layout.width, -layout.height); } // GlyphRun bounds for (int i = 0, n = layout.runs.size; i < n; i++) { if (i % 3 == 0) renderer.setColor(c, 0, c, 1); else if (i % 2 == 0) renderer.setColor(0, c, c, 1); else renderer.setColor(c, c, 0, 1); GlyphRun r = layout.runs.get(i); renderer.rect(10 + r.x, 10 + meowy + r.y, r.width, -font.getLineHeight()); } renderer.end(); font.getData().setScale(1f); return; } // Test wrapping with label. if (false) { label.debug(); label.getStyle().font = font; label.setStyle(label.getStyle()); label.setText(""How quickly [RED]daft[] jumping zebras vex.""); label.setWrap(true); // label.setEllipsis(true); label.setAlignment(Align.center, Align.right); label.setWidth(Gdx.input.getX() - label.getX()); label.setHeight(label.getPrefHeight()); } else { // Test various font features. spriteBatch.begin(); String text = ""Sphinx of black quartz, judge my vow.""; font.setColor(Color.RED); float x = 100, y = 20; float [MASK] ; if (false) { [MASK] = 0; font.draw(spriteBatch, text, x, viewHeight - y, [MASK] , Align.right, false); } if (true) { [MASK] = 280; font.draw(spriteBatch, text, x, viewHeight - y, [MASK] , Align.right, true); } font.draw(spriteBatch, ""["", 50, 60, 100, Align.left, true); font.getData().markupEnabled = true; font.draw(spriteBatch, ""["", 100, 60, 100, Align.left, true); font.getData().markupEnabled = false; // 'R' and 'p' are in different pages String txt2 = ""this font uses "" + multiPageFont.getRegions().size + "" texture pages: RpRpRpRpRpNM""; spriteBatch.renderCalls = 0; // regular draw function multiPageFont.setColor(Color.BLUE); multiPageFont.draw(spriteBatch, txt2, 10, 100); // expert usage.. drawing with bitmap font cache BitmapFontCache cache = multiPageFont.getCache(); cache.clear(); cache.setColor(Color.BLACK); cache.setText(txt2, 10, 50); cache.setColors(Color.PINK, 3, 6); cache.setColors(Color.ORANGE, 9, 12); cache.setColors(Color.GREEN, 16, txt2.length()); cache.draw(spriteBatch, 5, txt2.length() - 5); cache.clear(); cache.setColor(Color.BLACK); float textX = 10; textX += cache.setText(""[black] "", textX, 150).width; multiPageFont.getData().markupEnabled = true; textX += cache.addText(""[[[PINK]pink[]] "", textX, 150).width; textX += cache.addText(""[PERU][[peru] "", textX, 150).width; cache.setColor(Color.GREEN); textX += cache.addText(""green "", textX, 150).width; textX += cache.addText(""[#A52A2A]br[#A52A2ADF]ow[#A52A2ABF]n f[#A52A2A9F]ad[#A52A2A7F]in[#A52A2A5F]g o[#A52A2A3F]ut "", textX, 150).width; multiPageFont.getData().markupEnabled = false; cache.draw(spriteBatch); // tinting cache.tint(new Color(1f, 1f, 1f, 0.3f)); cache.translate(0f, 40f); cache.draw(spriteBatch); cache = smallFont.getCache(); // String neeeds to be pretty long to trigger the crash described in #5834; fixed now final String trogdor = ""TROGDOR! TROGDOR! Trogdor was a man! Or maybe he was a... Dragon-Man!""; cache.clear(); cache.addText(trogdor, 24, 37, 500, Align.center, true); cache.setColors(Color.FOREST, 0, trogdor.length()); cache.draw(spriteBatch); spriteBatch.end(); // System.out.println(spriteBatch.renderCalls); renderer.begin(ShapeType.Line); renderer.setColor(Color.BLACK); renderer.rect(x, viewHeight - y - 200, [MASK] , 200); renderer.end(); } stage.act(Gdx.graphics.getDeltaTime()); stage.draw(); } public void resize (int width, int height) { spriteBatch.getProjectionMatrix().setToOrtho2D(0, 0, width, height); renderer.setProjectionMatrix(spriteBatch.getProjectionMatrix()); stage.getViewport().update(width, height, true); } @Override public void dispose () { spriteBatch.dispose(); renderer.dispose(); font.dispose(); // Restore predefined colors Colors.reset(); } } ","alignmentWidth " "package com.google.inject.spi; import com.google.inject.AbstractModule; import com.google.inject.Asserts; import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.Stage; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import junit.framework.TestCase; public class ToolStageInjectorTest extends TestCase { @Override protected void setUp() throws Exception { Foo.s = null; Foo.sm = null; } public void testToolStageInjectorRestrictions() { Injector [MASK] = Guice.createInjector(Stage.TOOL); try { [MASK] .injectMembers(new Object()); fail(""Non-SPI Injector methods must throw an exception in the TOOL stage.""); } catch (UnsupportedOperationException expected) { } try { [MASK] .getInstance(Injector.class); fail(""Non-SPI Injector methods must throw an exception in the TOOL stage.""); } catch (UnsupportedOperationException expected) { } try { [MASK] .getInstance(Key.get(Injector.class)); fail(""Non-SPI Injector methods must throw an exception in the TOOL stage.""); } catch (UnsupportedOperationException expected) { } try { [MASK] .getProvider(Injector.class); fail(""Non-SPI Injector methods must throw an exception in the TOOL stage.""); } catch (UnsupportedOperationException expected) { } try { [MASK] .getProvider(Key.get(Injector.class)); fail(""Non-SPI Injector methods must throw an exception in the TOOL stage.""); } catch (UnsupportedOperationException expected) { } } public void testToolStageDoesntInjectInstances() { final Foo foo = new Foo(); Guice.createInjector( Stage.TOOL, new AbstractModule() { @Override protected void configure() { requestStaticInjection(Foo.class); requestInjection(foo); } }); assertNull(Foo.s); assertNull(Foo.sm); assertNull(foo.f); assertNull(foo.m); } public void testToolStageDoesntInjectProviders() { final Foo foo = new Foo(); Guice.createInjector( Stage.TOOL, new AbstractModule() { @Override protected void configure() { requestStaticInjection(Foo.class); bind(Object.class).toProvider(foo); } }); assertNull(Foo.s); assertNull(Foo.sm); assertNull(foo.f); assertNull(foo.m); } public void testToolStageWarnsOfMissingObjectGraph() { final Bar bar = new Bar(); try { Guice.createInjector( Stage.TOOL, new AbstractModule() { @Override protected void configure() { requestStaticInjection(Bar.class); requestInjection(bar); } }); fail(""expected exception""); } catch (CreationException expected) { Asserts.assertContains( expected.toString(), ""No implementation for Collection was bound."", ""No implementation for Map was bound."", ""No implementation for List was bound."", ""No implementation for Set was bound.""); } } public void testToolStageInjectsTooledMethods() { final Tooled tooled = new Tooled(); Guice.createInjector( Stage.TOOL, new AbstractModule() { @Override protected void configure() { requestStaticInjection(Tooled.class); bind(Object.class).toProvider(tooled); } }); assertNull(Tooled.s); assertNotNull(Tooled.sm); assertNull(tooled.f); assertNotNull(tooled.m); } private static class Bar { @SuppressWarnings(""unused"") @Inject private static List list; @SuppressWarnings(""unused"") @Inject private Set set; @SuppressWarnings(""unused"") @Inject void method(Collection c) {} @SuppressWarnings(""unused"") @Inject static void staticMethod(Map map) {} } private static class Foo implements Provider { @Inject private static S s; @Inject private F f; private M m; @SuppressWarnings(""unused"") @Inject void method(M m) { this.m = m; } private static SM sm; @SuppressWarnings(""unused"") @Inject static void staticMethod(SM sm) { Tooled.sm = sm; } @Override public Object get() { return null; } } private static class Tooled implements Provider { @Inject private static S s; @Inject private F f; private M m; @Toolable @SuppressWarnings(""unused"") @Inject void method(M m) { this.m = m; } private static SM sm; @Toolable @SuppressWarnings(""unused"") @Inject static void staticMethod(SM sm) { Tooled.sm = sm; } @Override public Object get() { return null; } } private static class S {} private static class F {} private static class M {} private static class SM {} } ","injector " " package org.jbox2d.particle; import java.util.Arrays; import org.jbox2d.callbacks.ParticleDestructionListener; import org.jbox2d.callbacks.ParticleQueryCallback; import org.jbox2d.callbacks.ParticleRaycastCallback; import org.jbox2d.callbacks.QueryCallback; import org.jbox2d.collision.AABB; import org.jbox2d.collision.RayCastInput; import org.jbox2d.collision.RayCastOutput; import org.jbox2d.collision.shapes.Shape; import org.jbox2d.common.BufferUtils; import org.jbox2d.common.MathUtils; import org.jbox2d.common.Rot; import org.jbox2d.common.Settings; import org.jbox2d.common.Transform; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.Fixture; import org.jbox2d.dynamics.TimeStep; import org.jbox2d.dynamics.World; import org.jbox2d.particle.VoronoiDiagram.VoronoiDiagramCallback; import com.badlogic.gdx.utils.reflect.ArrayReflection; import com.badlogic.gdx.utils.reflect.ClassReflection; public class ParticleSystem { /** All particle types that require creating pairs */ private static final int k_pairFlags = ParticleType.b2_springParticle; /** All particle types that require creating triads */ private static final int k_triadFlags = ParticleType.b2_elasticParticle; /** All particle types that require computing depth */ private static final int k_noPressureFlags = ParticleType.b2_powderParticle; static final int xTruncBits = 12; static final int yTruncBits = 12; static final int tagBits = 8 * 4 - 1 /* sizeof(int) */; static final long yOffset = 1 << (yTruncBits - 1); static final int yShift = tagBits - yTruncBits; static final int xShift = tagBits - yTruncBits - xTruncBits; static final long xScale = 1 << xShift; static final long xOffset = xScale * (1 << (xTruncBits - 1)); static final int xMask = (1 << xTruncBits) - 1; static final int yMask = (1 << yTruncBits) - 1; static long computeTag (float x, float y) { return (((long)(y + yOffset)) << yShift) + (((long)(xScale * x)) + xOffset); } static long computeRelativeTag (long tag, int x, int y) { return tag + (y << yShift) + (x << xShift); } static int limitCapacity (int capacity, int maxCount) { return maxCount != 0 && capacity > maxCount ? maxCount : capacity; } int m_timestamp; int m_allParticleFlags; int m_allGroupFlags; float m_density; float m_inverseDensity; float m_gravityScale; float m_particleDiameter; float m_inverseDiameter; float m_squaredDiameter; int m_count; int m_internalAllocatedCapacity; int m_maxCount; ParticleBufferInt m_flagsBuffer; ParticleBuffer m_positionBuffer; ParticleBuffer m_velocityBuffer; float[] m_accumulationBuffer; // temporary values Vec2[] m_accumulation2Buffer; // temporary vector values float[] m_depthBuffer; // distance from the surface public ParticleBuffer m_colorBuffer; ParticleGroup[] m_groupBuffer; ParticleBuffer m_userDataBuffer; int m_proxyCount; int m_proxyCapacity; Proxy[] m_proxyBuffer; public int m_contactCount; int m_contactCapacity; public ParticleContact[] m_contactBuffer; public int m_bodyContactCount; int m_bodyContactCapacity; public ParticleBodyContact[] m_bodyContactBuffer; int m_pairCount; int m_pairCapacity; Pair[] m_pairBuffer; int m_triadCount; int m_triadCapacity; Triad[] m_triadBuffer; int m_groupCount; ParticleGroup m_groupList; float m_pressureStrength; float m_dampingStrength; float m_elasticStrength; float m_springStrength; float m_viscousStrength; float m_surfaceTensionStrengthA; float m_surfaceTensionStrengthB; float m_powderStrength; float m_ejectionStrength; float m_colorMixingStrength; World m_world; public ParticleSystem (World world) { m_world = world; m_timestamp = 0; m_allParticleFlags = 0; m_allGroupFlags = 0; m_density = 1; m_inverseDensity = 1; m_gravityScale = 1; m_particleDiameter = 1; m_inverseDiameter = 1; m_squaredDiameter = 1; m_count = 0; m_internalAllocatedCapacity = 0; m_maxCount = 0; m_proxyCount = 0; m_proxyCapacity = 0; m_contactCount = 0; m_contactCapacity = 0; m_bodyContactCount = 0; m_bodyContactCapacity = 0; m_pairCount = 0; m_pairCapacity = 0; m_triadCount = 0; m_triadCapacity = 0; m_groupCount = 0; m_pressureStrength = 0.05f; m_dampingStrength = 1.0f; m_elasticStrength = 0.25f; m_springStrength = 0.25f; m_viscousStrength = 0.25f; m_surfaceTensionStrengthA = 0.1f; m_surfaceTensionStrengthB = 0.2f; m_powderStrength = 0.5f; m_ejectionStrength = 0.5f; m_colorMixingStrength = 0.5f; m_flagsBuffer = new ParticleBufferInt(); m_positionBuffer = new ParticleBuffer(Vec2.class); m_velocityBuffer = new ParticleBuffer(Vec2.class); m_colorBuffer = new ParticleBuffer(ParticleColor.class); m_userDataBuffer = new ParticleBuffer(Object.class); } // public void assertNotSamePosition() { // for (int i = 0; i < m_count; i++) { // Vec2 vi = m_positionBuffer.data[i]; // for (int j = i + 1; j < m_count; j++) { // Vec2 vj = m_positionBuffer.data[j]; // assert(vi.x != vj.x || vi.y != vj.y); // } // } // } public int createParticle (ParticleDef def) { if (m_count >= m_internalAllocatedCapacity) { int capacity = m_count != 0 ? 2 * m_count : Settings.minParticleBufferCapacity; capacity = limitCapacity(capacity, m_maxCount); capacity = limitCapacity(capacity, m_flagsBuffer.userSuppliedCapacity); capacity = limitCapacity(capacity, m_positionBuffer.userSuppliedCapacity); capacity = limitCapacity(capacity, m_velocityBuffer.userSuppliedCapacity); capacity = limitCapacity(capacity, m_colorBuffer.userSuppliedCapacity); capacity = limitCapacity(capacity, m_userDataBuffer.userSuppliedCapacity); if (m_internalAllocatedCapacity < capacity) { m_flagsBuffer.data = reallocateBuffer(m_flagsBuffer, m_internalAllocatedCapacity, capacity, false); m_positionBuffer.data = reallocateBuffer(m_positionBuffer, m_internalAllocatedCapacity, capacity, false); m_velocityBuffer.data = reallocateBuffer(m_velocityBuffer, m_internalAllocatedCapacity, capacity, false); m_accumulationBuffer = BufferUtils.reallocateBuffer(m_accumulationBuffer, 0, m_internalAllocatedCapacity, capacity, false); m_accumulation2Buffer = BufferUtils.reallocateBuffer(Vec2.class, m_accumulation2Buffer, 0, m_internalAllocatedCapacity, capacity, true); m_depthBuffer = BufferUtils.reallocateBuffer(m_depthBuffer, 0, m_internalAllocatedCapacity, capacity, true); m_colorBuffer.data = reallocateBuffer(m_colorBuffer, m_internalAllocatedCapacity, capacity, true); m_groupBuffer = BufferUtils.reallocateBuffer(ParticleGroup.class, m_groupBuffer, 0, m_internalAllocatedCapacity, capacity, false); m_userDataBuffer.data = reallocateBuffer(m_userDataBuffer, m_internalAllocatedCapacity, capacity, true); m_internalAllocatedCapacity = capacity; } } if (m_count >= m_internalAllocatedCapacity) { return Settings.invalidParticleIndex; } int index = m_count++; m_flagsBuffer.data[index] = def.flags; m_positionBuffer.data[index].set(def.position); // assertNotSamePosition(); m_velocityBuffer.data[index].set(def.velocity); m_groupBuffer[index] = null; if (m_depthBuffer != null) { m_depthBuffer[index] = 0; } if (m_colorBuffer.data != null || def.color != null) { m_colorBuffer.data = requestParticleBuffer(m_colorBuffer.dataClass, m_colorBuffer.data); m_colorBuffer.data[index].set(def.color); } if (m_userDataBuffer.data != null || def.userData != null) { m_userDataBuffer.data = requestParticleBuffer(m_userDataBuffer.dataClass, m_userDataBuffer.data); m_userDataBuffer.data[index] = def.userData; } if (m_proxyCount >= m_proxyCapacity) { int oldCapacity = m_proxyCapacity; int newCapacity = m_proxyCount != 0 ? 2 * m_proxyCount : Settings.minParticleBufferCapacity; m_proxyBuffer = BufferUtils.reallocateBuffer(Proxy.class, m_proxyBuffer, oldCapacity, newCapacity); m_proxyCapacity = newCapacity; } m_proxyBuffer[m_proxyCount++].index = index; return index; } public void destroyParticle (int index, boolean callDestructionListener) { int flags = ParticleType.b2_zombieParticle; if (callDestructionListener) { flags |= ParticleType.b2_destructionListener; } m_flagsBuffer.data[index] |= flags; } private final AABB temp = new AABB(); private final DestroyParticlesInShapeCallback dpcallback = new DestroyParticlesInShapeCallback(); public int destroyParticlesInShape (Shape shape, Transform xf, boolean callDestructionListener) { dpcallback.init(this, shape, xf, callDestructionListener); shape.computeAABB(temp, xf, 0); m_world.queryAABB(dpcallback, temp); return dpcallback.destroyed; } public void destroyParticlesInGroup (ParticleGroup group, boolean callDestructionListener) { for (int i = group.m_firstIndex; i < group.m_lastIndex; i++) { destroyParticle(i, callDestructionListener); } } private final AABB temp2 = new AABB(); private final Vec2 tempVec = new Vec2(); private final Transform tempTransform = new Transform(); private final Transform tempTransform2 = new Transform(); private CreateParticleGroupCallback createParticleGroupCallback = new CreateParticleGroupCallback(); private final ParticleDef tempParticleDef = new ParticleDef(); public ParticleGroup createParticleGroup (ParticleGroupDef groupDef) { float stride = getParticleStride(); final Transform identity = tempTransform; identity.setIdentity(); Transform transform = tempTransform2; transform.setIdentity(); int firstIndex = m_count; if (groupDef.shape != null) { final ParticleDef particleDef = tempParticleDef; particleDef.flags = groupDef.flags; particleDef.color = groupDef.color; particleDef.userData = groupDef.userData; Shape shape = groupDef.shape; transform.set(groupDef.position, groupDef.angle); AABB aabb = temp; int childCount = shape.getChildCount(); for (int childIndex = 0; childIndex < childCount; childIndex++) { if (childIndex == 0) { shape.computeAABB(aabb, identity, childIndex); } else { AABB childAABB = temp2; shape.computeAABB(childAABB, identity, childIndex); aabb.combine(childAABB); } } final float upperBoundY = aabb.upperBound.y; final float upperBoundX = aabb.upperBound.x; for (float y = MathUtils.floor(aabb.lowerBound.y / stride) * stride; y < upperBoundY; y += stride) { for (float x = MathUtils.floor(aabb.lowerBound.x / stride) * stride; x < upperBoundX; x += stride) { Vec2 p = tempVec; p.x = x; p.y = y; if (shape.testPoint(identity, p)) { Transform.mulToOut(transform, p, p); particleDef.position.x = p.x; particleDef.position.y = p.y; p.subLocal(groupDef.position); Vec2.crossToOutUnsafe(groupDef.angularVelocity, p, particleDef.velocity); particleDef.velocity.addLocal(groupDef.linearVelocity); createParticle(particleDef); } } } } int lastIndex = m_count; ParticleGroup group = new ParticleGroup(); group.m_system = this; group.m_firstIndex = firstIndex; group.m_lastIndex = lastIndex; group.m_groupFlags = groupDef.groupFlags; group.m_strength = groupDef.strength; group.m_userData = groupDef.userData; group.m_transform.set(transform); group.m_destroyAutomatically = groupDef.destroyAutomatically; group.m_prev = null; group.m_next = m_groupList; if (m_groupList != null) { m_groupList.m_prev = group; } m_groupList = group; ++m_groupCount; for (int i = firstIndex; i < lastIndex; i++) { m_groupBuffer[i] = group; } updateContacts(true); if ((groupDef.flags & k_pairFlags) != 0) { for (int k = 0; k < m_contactCount; k++) { ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; if (a > b) { int temp = a; a = b; b = temp; } if (firstIndex <= a && b < lastIndex) { if (m_pairCount >= m_pairCapacity) { int oldCapacity = m_pairCapacity; int newCapacity = m_pairCount != 0 ? 2 * m_pairCount : Settings.minParticleBufferCapacity; m_pairBuffer = BufferUtils.reallocateBuffer(Pair.class, m_pairBuffer, oldCapacity, newCapacity); m_pairCapacity = newCapacity; } Pair pair = m_pairBuffer[m_pairCount]; pair.indexA = a; pair.indexB = b; pair.flags = contact.flags; pair.strength = groupDef.strength; pair.distance = MathUtils.distance(m_positionBuffer.data[a], m_positionBuffer.data[b]); m_pairCount++; } } } if ((groupDef.flags & k_triadFlags) != 0) { VoronoiDiagram diagram = new VoronoiDiagram(lastIndex - firstIndex); for (int i = firstIndex; i < lastIndex; i++) { diagram.addGenerator(m_positionBuffer.data[i], i); } diagram.generate(stride / 2); createParticleGroupCallback.system = this; createParticleGroupCallback.def = groupDef; createParticleGroupCallback.firstIndex = firstIndex; diagram.getNodes(createParticleGroupCallback); } if ((groupDef.groupFlags & ParticleGroupType.b2_solidParticleGroup) != 0) { computeDepthForGroup(group); } return group; } public void joinParticleGroups (ParticleGroup groupA, ParticleGroup groupB) { assert (groupA != groupB); RotateBuffer(groupB.m_firstIndex, groupB.m_lastIndex, m_count); assert (groupB.m_lastIndex == m_count); RotateBuffer(groupA.m_firstIndex, groupA.m_lastIndex, groupB.m_firstIndex); assert (groupA.m_lastIndex == groupB.m_firstIndex); int particleFlags = 0; for (int i = groupA.m_firstIndex; i < groupB.m_lastIndex; i++) { particleFlags |= m_flagsBuffer.data[i]; } updateContacts(true); if ((particleFlags & k_pairFlags) != 0) { for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; if (a > b) { int temp = a; a = b; b = temp; } if (groupA.m_firstIndex <= a && a < groupA.m_lastIndex && groupB.m_firstIndex <= b && b < groupB.m_lastIndex) { if (m_pairCount >= m_pairCapacity) { int oldCapacity = m_pairCapacity; int newCapacity = m_pairCount != 0 ? 2 * m_pairCount : Settings.minParticleBufferCapacity; m_pairBuffer = BufferUtils.reallocateBuffer(Pair.class, m_pairBuffer, oldCapacity, newCapacity); m_pairCapacity = newCapacity; } Pair pair = m_pairBuffer[m_pairCount]; pair.indexA = a; pair.indexB = b; pair.flags = contact.flags; pair.strength = MathUtils.min(groupA.m_strength, groupB.m_strength); pair.distance = MathUtils.distance(m_positionBuffer.data[a], m_positionBuffer.data[b]); m_pairCount++; } } } if ((particleFlags & k_triadFlags) != 0) { VoronoiDiagram diagram = new VoronoiDiagram(groupB.m_lastIndex - groupA.m_firstIndex); for (int i = groupA.m_firstIndex; i < groupB.m_lastIndex; i++) { if ((m_flagsBuffer.data[i] & ParticleType.b2_zombieParticle) == 0) { diagram.addGenerator(m_positionBuffer.data[i], i); } } diagram.generate(getParticleStride() / 2); JoinParticleGroupsCallback callback = new JoinParticleGroupsCallback(); callback.system = this; callback.groupA = groupA; callback.groupB = groupB; diagram.getNodes(callback); } for (int i = groupB.m_firstIndex; i < groupB.m_lastIndex; i++) { m_groupBuffer[i] = groupA; } int groupFlags = groupA.m_groupFlags | groupB.m_groupFlags; groupA.m_groupFlags = groupFlags; groupA.m_lastIndex = groupB.m_lastIndex; groupB.m_firstIndex = groupB.m_lastIndex; destroyParticleGroup(groupB); if ((groupFlags & ParticleGroupType.b2_solidParticleGroup) != 0) { computeDepthForGroup(groupA); } } // Only called from solveZombie() or joinParticleGroups(). void destroyParticleGroup (ParticleGroup group) { assert (m_groupCount > 0); assert (group != null); if (m_world.getParticleDestructionListener() != null) { m_world.getParticleDestructionListener().sayGoodbye(group); } for (int i = group.m_firstIndex; i < group.m_lastIndex; i++) { m_groupBuffer[i] = null; } if (group.m_prev != null) { group.m_prev.m_next = group.m_next; } if (group.m_next != null) { group.m_next.m_prev = group.m_prev; } if (group == m_groupList) { m_groupList = group.m_next; } --m_groupCount; } public void computeDepthForGroup (ParticleGroup group) { for (int i = group.m_firstIndex; i < group.m_lastIndex; i++) { m_accumulationBuffer[i] = 0; } for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; if (a >= group.m_firstIndex && a < group.m_lastIndex && b >= group.m_firstIndex && b < group.m_lastIndex) { float w = contact.weight; m_accumulationBuffer[a] += w; m_accumulationBuffer[b] += w; } } m_depthBuffer = requestParticleBuffer(m_depthBuffer); for (int i = group.m_firstIndex; i < group.m_lastIndex; i++) { float w = m_accumulationBuffer[i]; m_depthBuffer[i] = w < 0.8f ? 0 : Float.MAX_VALUE; } int interationCount = group.getParticleCount(); for (int t = 0; t < interationCount; t++) { boolean updated = false; for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; if (a >= group.m_firstIndex && a < group.m_lastIndex && b >= group.m_firstIndex && b < group.m_lastIndex) { float r = 1 - contact.weight; float ap0 = m_depthBuffer[a]; float bp0 = m_depthBuffer[b]; float ap1 = bp0 + r; float bp1 = ap0 + r; if (ap0 > ap1) { m_depthBuffer[a] = ap1; updated = true; } if (bp0 > bp1) { m_depthBuffer[b] = bp1; updated = true; } } } if (!updated) { break; } } for (int i = group.m_firstIndex; i < group.m_lastIndex; i++) { float p = m_depthBuffer[i]; if (p < Float.MAX_VALUE) { m_depthBuffer[i] *= m_particleDiameter; } else { m_depthBuffer[i] = 0; } } } public void addContact (int a, int b) { assert (a != b); Vec2 pa = m_positionBuffer.data[a]; Vec2 pb = m_positionBuffer.data[b]; float dx = pb.x - pa.x; float dy = pb.y - pa.y; float d2 = dx * dx + dy * dy; // assert(d2 != 0); if (d2 < m_squaredDiameter) { if (m_contactCount >= m_contactCapacity) { int oldCapacity = m_contactCapacity; int newCapacity = m_contactCount != 0 ? 2 * m_contactCount : Settings.minParticleBufferCapacity; m_contactBuffer = BufferUtils.reallocateBuffer(ParticleContact.class, m_contactBuffer, oldCapacity, newCapacity); m_contactCapacity = newCapacity; } float invD = d2 != 0 ? MathUtils.sqrt(1 / d2) : Float.MAX_VALUE; ParticleContact contact = m_contactBuffer[m_contactCount]; contact.indexA = a; contact.indexB = b; contact.flags = m_flagsBuffer.data[a] | m_flagsBuffer.data[b]; contact.weight = 1 - d2 * invD * m_inverseDiameter; contact.normal.x = invD * dx; contact.normal.y = invD * dy; m_contactCount++; } } public void updateContacts (boolean exceptZombie) { for (int p = 0; p < m_proxyCount; p++) { Proxy proxy = m_proxyBuffer[p]; int i = proxy.index; Vec2 pos = m_positionBuffer.data[i]; proxy.tag = computeTag(m_inverseDiameter * pos.x, m_inverseDiameter * pos.y); } Arrays.sort(m_proxyBuffer, 0, m_proxyCount); m_contactCount = 0; int c_index = 0; for (int i = 0; i < m_proxyCount; i++) { Proxy a = m_proxyBuffer[i]; long rightTag = computeRelativeTag(a.tag, 1, 0); for (int j = i + 1; j < m_proxyCount; j++) { Proxy b = m_proxyBuffer[j]; if (rightTag < b.tag) { break; } addContact(a.index, b.index); } long bottomLeftTag = computeRelativeTag(a.tag, -1, 1); for (; c_index < m_proxyCount; c_index++) { Proxy c = m_proxyBuffer[c_index]; if (bottomLeftTag <= c.tag) { break; } } long bottomRightTag = computeRelativeTag(a.tag, 1, 1); for (int b_index = c_index; b_index < m_proxyCount; b_index++) { Proxy b = m_proxyBuffer[b_index]; if (bottomRightTag < b.tag) { break; } addContact(a.index, b.index); } } if (exceptZombie) { int j = m_contactCount; for (int i = 0; i < j; i++) { if ((m_contactBuffer[i].flags & ParticleType.b2_zombieParticle) != 0) { --j; ParticleContact temp = m_contactBuffer[j]; m_contactBuffer[j] = m_contactBuffer[i]; m_contactBuffer[i] = temp; --i; } } m_contactCount = j; } } private final UpdateBodyContactsCallback ubccallback = new UpdateBodyContactsCallback(); public void updateBodyContacts () { final AABB aabb = temp; aabb.lowerBound.x = Float.MAX_VALUE; aabb.lowerBound.y = Float.MAX_VALUE; aabb.upperBound.x = -Float.MAX_VALUE; aabb.upperBound.y = -Float.MAX_VALUE; for (int i = 0; i < m_count; i++) { Vec2 p = m_positionBuffer.data[i]; Vec2.minToOut(aabb.lowerBound, p, aabb.lowerBound); Vec2.maxToOut(aabb.upperBound, p, aabb.upperBound); } aabb.lowerBound.x -= m_particleDiameter; aabb.lowerBound.y -= m_particleDiameter; aabb.upperBound.x += m_particleDiameter; aabb.upperBound.y += m_particleDiameter; m_bodyContactCount = 0; ubccallback.system = this; m_world.queryAABB(ubccallback, aabb); } private SolveCollisionCallback sccallback = new SolveCollisionCallback(); public void solveCollision (TimeStep step) { final AABB aabb = temp; final Vec2 lowerBound = aabb.lowerBound; final Vec2 upperBound = aabb.upperBound; lowerBound.x = Float.MAX_VALUE; lowerBound.y = Float.MAX_VALUE; upperBound.x = -Float.MAX_VALUE; upperBound.y = -Float.MAX_VALUE; for (int i = 0; i < m_count; i++) { final Vec2 v = m_velocityBuffer.data[i]; final Vec2 p1 = m_positionBuffer.data[i]; final float p1x = p1.x; final float p1y = p1.y; final float p2x = p1x + step.dt * v.x; final float p2y = p1y + step.dt * v.y; final float bx = p1x < p2x ? p1x : p2x; final float by = p1y < p2y ? p1y : p2y; lowerBound.x = lowerBound.x < bx ? lowerBound.x : bx; lowerBound.y = lowerBound.y < by ? lowerBound.y : by; final float b1x = p1x > p2x ? p1x : p2x; final float b1y = p1y > p2y ? p1y : p2y; upperBound.x = upperBound.x > b1x ? upperBound.x : b1x; upperBound.y = upperBound.y > b1y ? upperBound.y : b1y; } sccallback.step = step; sccallback.system = this; m_world.queryAABB(sccallback, aabb); } public void solve (TimeStep step) { ++m_timestamp; if (m_count == 0) { return; } m_allParticleFlags = 0; for (int i = 0; i < m_count; i++) { m_allParticleFlags |= m_flagsBuffer.data[i]; } if ((m_allParticleFlags & ParticleType.b2_zombieParticle) != 0) { solveZombie(); } if (m_count == 0) { return; } m_allGroupFlags = 0; for (ParticleGroup group = m_groupList; group != null; group = group.getNext()) { m_allGroupFlags |= group.m_groupFlags; } final float gravityx = step.dt * m_gravityScale * m_world.getGravity().x; final float gravityy = step.dt * m_gravityScale * m_world.getGravity().y; float criticalVelocytySquared = getCriticalVelocitySquared(step); for (int i = 0; i < m_count; i++) { Vec2 v = m_velocityBuffer.data[i]; v.x += gravityx; v.y += gravityy; float v2 = v.x * v.x + v.y * v.y; if (v2 > criticalVelocytySquared) { float a = v2 == 0 ? Float.MAX_VALUE : MathUtils.sqrt(criticalVelocytySquared / v2); v.x *= a; v.y *= a; } } solveCollision(step); if ((m_allGroupFlags & ParticleGroupType.b2_rigidParticleGroup) != 0) { solveRigid(step); } if ((m_allParticleFlags & ParticleType.b2_wallParticle) != 0) { solveWall(step); } for (int i = 0; i < m_count; i++) { Vec2 pos = m_positionBuffer.data[i]; Vec2 vel = m_velocityBuffer.data[i]; pos.x += step.dt * vel.x; pos.y += step.dt * vel.y; } updateBodyContacts(); updateContacts(false); if ((m_allParticleFlags & ParticleType.b2_viscousParticle) != 0) { solveViscous(step); } if ((m_allParticleFlags & ParticleType.b2_powderParticle) != 0) { solvePowder(step); } if ((m_allParticleFlags & ParticleType.b2_tensileParticle) != 0) { solveTensile(step); } if ((m_allParticleFlags & ParticleType.b2_elasticParticle) != 0) { solveElastic(step); } if ((m_allParticleFlags & ParticleType.b2_springParticle) != 0) { solveSpring(step); } if ((m_allGroupFlags & ParticleGroupType.b2_solidParticleGroup) != 0) { solveSolid(step); } if ((m_allParticleFlags & ParticleType.b2_colorMixingParticle) != 0) { solveColorMixing(step); } solvePressure(step); solveDamping(step); } void solvePressure (TimeStep step) { // calculates the sum of contact-weights for each particle // that means dimensionless density for (int i = 0; i < m_count; i++) { m_accumulationBuffer[i] = 0; } for (int k = 0; k < m_bodyContactCount; k++) { ParticleBodyContact contact = m_bodyContactBuffer[k]; int a = contact.index; float w = contact.weight; m_accumulationBuffer[a] += w; } for (int k = 0; k < m_contactCount; k++) { ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; float w = contact.weight; m_accumulationBuffer[a] += w; m_accumulationBuffer[b] += w; } // ignores powder particles if ((m_allParticleFlags & k_noPressureFlags) != 0) { for (int i = 0; i < m_count; i++) { if ((m_flagsBuffer.data[i] & k_noPressureFlags) != 0) { m_accumulationBuffer[i] = 0; } } } // calculates pressure as a linear function of density float pressurePerWeight = m_pressureStrength * getCriticalPressure(step); for (int i = 0; i < m_count; i++) { float w = m_accumulationBuffer[i]; float h = pressurePerWeight * MathUtils.max(0.0f, MathUtils.min(w, Settings.maxParticleWeight) - Settings.minParticleWeight); m_accumulationBuffer[i] = h; } // applies pressure between each particles in contact float velocityPerPressure = step.dt / (m_density * m_particleDiameter); for (int k = 0; k < m_bodyContactCount; k++) { ParticleBodyContact contact = m_bodyContactBuffer[k]; int a = contact.index; Body b = contact.body; float w = contact.weight; float m = contact.mass; Vec2 n = contact.normal; Vec2 p = m_positionBuffer.data[a]; float h = m_accumulationBuffer[a] + pressurePerWeight * w; final Vec2 f = tempVec; final float coef = velocityPerPressure * w * m * h; f.x = coef * n.x; f.y = coef * n.y; final Vec2 velData = m_velocityBuffer.data[a]; final float particleInvMass = getParticleInvMass(); velData.x -= particleInvMass * f.x; velData.y -= particleInvMass * f.y; b.applyLinearImpulse(f, p, true); } for (int k = 0; k < m_contactCount; k++) { ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; float w = contact.weight; Vec2 n = contact.normal; float h = m_accumulationBuffer[a] + m_accumulationBuffer[b]; final float fx = velocityPerPressure * w * h * n.x; final float fy = velocityPerPressure * w * h * n.y; final Vec2 velDataA = m_velocityBuffer.data[a]; final Vec2 velDataB = m_velocityBuffer.data[b]; velDataA.x -= fx; velDataA.y -= fy; velDataB.x += fx; velDataB.y += fy; } } void solveDamping (TimeStep step) { // reduces normal velocity of each contact float damping = m_dampingStrength; for (int k = 0; k < m_bodyContactCount; k++) { final ParticleBodyContact contact = m_bodyContactBuffer[k]; int a = contact.index; Body b = contact.body; float w = contact.weight; float m = contact.mass; Vec2 n = contact.normal; Vec2 p = m_positionBuffer.data[a]; final float tempX = p.x - b.m_sweep.c.x; final float tempY = p.y - b.m_sweep.c.y; final Vec2 velA = m_velocityBuffer.data[a]; // getLinearVelocityFromWorldPointToOut, with -= velA float vx = -b.m_angularVelocity * tempY + b.m_linearVelocity.x - velA.x; float vy = b.m_angularVelocity * tempX + b.m_linearVelocity.y - velA.y; // done float vn = vx * n.x + vy * n.y; if (vn < 0) { final Vec2 f = tempVec; f.x = damping * w * m * vn * n.x; f.y = damping * w * m * vn * n.y; final float invMass = getParticleInvMass(); velA.x += invMass * f.x; velA.y += invMass * f.y; f.x = -f.x; f.y = -f.y; b.applyLinearImpulse(f, p, true); } } for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; float w = contact.weight; Vec2 n = contact.normal; final Vec2 velA = m_velocityBuffer.data[a]; final Vec2 velB = m_velocityBuffer.data[b]; final float vx = velB.x - velA.x; final float vy = velB.y - velA.y; float vn = vx * n.x + vy * n.y; if (vn < 0) { float fx = damping * w * vn * n.x; float fy = damping * w * vn * n.y; velA.x += fx; velA.y += fy; velB.x -= fx; velB.y -= fy; } } } public void solveWall (TimeStep step) { for (int i = 0; i < m_count; i++) { if ((m_flagsBuffer.data[i] & ParticleType.b2_wallParticle) != 0) { final Vec2 r = m_velocityBuffer.data[i]; r.x = 0.0f; r.y = 0.0f; } } } private final Vec2 tempVec2 = new Vec2(); private final Rot tempRot = new Rot(); private final Transform tempXf = new Transform(); private final Transform tempXf2 = new Transform(); void solveRigid (final TimeStep step) { for (ParticleGroup group = m_groupList; group != null; group = group.getNext()) { if ((group.m_groupFlags & ParticleGroupType.b2_rigidParticleGroup) != 0) { group.updateStatistics(); Vec2 temp = tempVec; Vec2 cross = tempVec2; Rot rotation = tempRot; rotation.set(step.dt * group.m_angularVelocity); Rot.mulToOutUnsafe(rotation, group.m_center, cross); temp.set(group.m_linearVelocity).mulLocal(step.dt).addLocal(group.m_center).subLocal(cross); tempXf.p.set(temp); tempXf.q.set(rotation); Transform.mulToOut(tempXf, group.m_transform, group.m_transform); final Transform velocityTransform = tempXf2; velocityTransform.p.x = step.inv_dt * tempXf.p.x; velocityTransform.p.y = step.inv_dt * tempXf.p.y; velocityTransform.q.s = step.inv_dt * tempXf.q.s; velocityTransform.q.c = step.inv_dt * (tempXf.q.c - 1); for (int i = group.m_firstIndex; i < group.m_lastIndex; i++) { Transform.mulToOutUnsafe(velocityTransform, m_positionBuffer.data[i], m_velocityBuffer.data[i]); } } } } void solveElastic (final TimeStep step) { float elasticStrength = step.inv_dt * m_elasticStrength; for (int k = 0; k < m_triadCount; k++) { final Triad triad = m_triadBuffer[k]; if ((triad.flags & ParticleType.b2_elasticParticle) != 0) { int a = triad.indexA; int b = triad.indexB; int c = triad.indexC; final Vec2 oa = triad.pa; final Vec2 ob = triad.pb; final Vec2 oc = triad.pc; final Vec2 pa = m_positionBuffer.data[a]; final Vec2 pb = m_positionBuffer.data[b]; final Vec2 pc = m_positionBuffer.data[c]; final float px = 1f / 3 * (pa.x + pb.x + pc.x); final float py = 1f / 3 * (pa.y + pb.y + pc.y); float rs = Vec2.cross(oa, pa) + Vec2.cross(ob, pb) + Vec2.cross(oc, pc); float rc = Vec2.dot(oa, pa) + Vec2.dot(ob, pb) + Vec2.dot(oc, pc); float r2 = rs * rs + rc * rc; float invR = r2 == 0 ? Float.MAX_VALUE : MathUtils.sqrt(1f / r2); rs *= invR; rc *= invR; final float strength = elasticStrength * triad.strength; final float roax = rc * oa.x - rs * oa.y; final float roay = rs * oa.x + rc * oa.y; final float robx = rc * ob.x - rs * ob.y; final float roby = rs * ob.x + rc * ob.y; final float rocx = rc * oc.x - rs * oc.y; final float rocy = rs * oc.x + rc * oc.y; final Vec2 va = m_velocityBuffer.data[a]; final Vec2 vb = m_velocityBuffer.data[b]; final Vec2 vc = m_velocityBuffer.data[c]; va.x += strength * (roax - (pa.x - px)); va.y += strength * (roay - (pa.y - py)); vb.x += strength * (robx - (pb.x - px)); vb.y += strength * (roby - (pb.y - py)); vc.x += strength * (rocx - (pc.x - px)); vc.y += strength * (rocy - (pc.y - py)); } } } void solveSpring (final TimeStep step) { float springStrength = step.inv_dt * m_springStrength; for (int k = 0; k < m_pairCount; k++) { final Pair pair = m_pairBuffer[k]; if ((pair.flags & ParticleType.b2_springParticle) != 0) { int a = pair.indexA; int b = pair.indexB; final Vec2 pa = m_positionBuffer.data[a]; final Vec2 pb = m_positionBuffer.data[b]; final float dx = pb.x - pa.x; final float dy = pb.y - pa.y; float r0 = pair.distance; float r1 = MathUtils.sqrt(dx * dx + dy * dy); if (r1 == 0) r1 = Float.MAX_VALUE; float strength = springStrength * pair.strength; final float fx = strength * (r0 - r1) / r1 * dx; final float fy = strength * (r0 - r1) / r1 * dy; final Vec2 va = m_velocityBuffer.data[a]; final Vec2 vb = m_velocityBuffer.data[b]; va.x -= fx; va.y -= fy; vb.x += fx; vb.y += fy; } } } void solveTensile (final TimeStep step) { m_accumulation2Buffer = requestParticleBuffer(Vec2.class, m_accumulation2Buffer); for (int i = 0; i < m_count; i++) { m_accumulationBuffer[i] = 0; m_accumulation2Buffer[i].setZero(); } for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; if ((contact.flags & ParticleType.b2_tensileParticle) != 0) { int a = contact.indexA; int b = contact.indexB; float w = contact.weight; Vec2 n = contact.normal; m_accumulationBuffer[a] += w; m_accumulationBuffer[b] += w; final Vec2 a2A = m_accumulation2Buffer[a]; final Vec2 a2B = m_accumulation2Buffer[b]; final float inter = (1 - w) * w; a2A.x -= inter * n.x; a2A.y -= inter * n.y; a2B.x += inter * n.x; a2B.y += inter * n.y; } } float strengthA = m_surfaceTensionStrengthA * getCriticalVelocity(step); float strengthB = m_surfaceTensionStrengthB * getCriticalVelocity(step); for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; if ((contact.flags & ParticleType.b2_tensileParticle) != 0) { int a = contact.indexA; int b = contact.indexB; float w = contact.weight; Vec2 n = contact.normal; final Vec2 a2A = m_accumulation2Buffer[a]; final Vec2 a2B = m_accumulation2Buffer[b]; float h = m_accumulationBuffer[a] + m_accumulationBuffer[b]; final float sx = a2B.x - a2A.x; final float sy = a2B.y - a2A.y; float fn = (strengthA * (h - 2) + strengthB * (sx * n.x + sy * n.y)) * w; final float fx = fn * n.x; final float fy = fn * n.y; final Vec2 va = m_velocityBuffer.data[a]; final Vec2 vb = m_velocityBuffer.data[b]; va.x -= fx; va.y -= fy; vb.x += fx; vb.y += fy; } } } void solveViscous (final TimeStep step) { float viscousStrength = m_viscousStrength; for (int k = 0; k < m_bodyContactCount; k++) { final ParticleBodyContact contact = m_bodyContactBuffer[k]; int a = contact.index; if ((m_flagsBuffer.data[a] & ParticleType.b2_viscousParticle) != 0) { Body b = contact.body; float w = contact.weight; float m = contact.mass; Vec2 p = m_positionBuffer.data[a]; final Vec2 va = m_velocityBuffer.data[a]; final float tempX = p.x - b.m_sweep.c.x; final float tempY = p.y - b.m_sweep.c.y; final float vx = -b.m_angularVelocity * tempY + b.m_linearVelocity.x - va.x; final float vy = b.m_angularVelocity * tempX + b.m_linearVelocity.y - va.y; final Vec2 f = tempVec; final float pInvMass = getParticleInvMass(); f.x = viscousStrength * m * w * vx; f.y = viscousStrength * m * w * vy; va.x += pInvMass * f.x; va.y += pInvMass * f.y; f.x = -f.x; f.y = -f.y; b.applyLinearImpulse(f, p, true); } } for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; if ((contact.flags & ParticleType.b2_viscousParticle) != 0) { int a = contact.indexA; int b = contact.indexB; float w = contact.weight; final Vec2 va = m_velocityBuffer.data[a]; final Vec2 vb = m_velocityBuffer.data[b]; final float vx = vb.x - va.x; final float vy = vb.y - va.y; final float fx = viscousStrength * w * vx; final float fy = viscousStrength * w * vy; va.x += fx; va.y += fy; vb.x -= fx; vb.y -= fy; } } } void solvePowder (final TimeStep step) { float powderStrength = m_powderStrength * getCriticalVelocity(step); float minWeight = 1.0f - Settings.particleStride; for (int k = 0; k < m_bodyContactCount; k++) { final ParticleBodyContact contact = m_bodyContactBuffer[k]; int a = contact.index; if ((m_flagsBuffer.data[a] & ParticleType.b2_powderParticle) != 0) { float w = contact.weight; if (w > minWeight) { Body b = contact.body; float m = contact.mass; Vec2 p = m_positionBuffer.data[a]; Vec2 n = contact.normal; final Vec2 f = tempVec; final Vec2 va = m_velocityBuffer.data[a]; final float inter = powderStrength * m * (w - minWeight); final float pInvMass = getParticleInvMass(); f.x = inter * n.x; f.y = inter * n.y; va.x -= pInvMass * f.x; va.y -= pInvMass * f.y; b.applyLinearImpulse(f, p, true); } } } for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; if ((contact.flags & ParticleType.b2_powderParticle) != 0) { float w = contact.weight; if (w > minWeight) { int a = contact.indexA; int b = contact.indexB; Vec2 n = contact.normal; final Vec2 va = m_velocityBuffer.data[a]; final Vec2 vb = m_velocityBuffer.data[b]; final float inter = powderStrength * (w - minWeight); final float fx = inter * n.x; final float fy = inter * n.y; va.x -= fx; va.y -= fy; vb.x += fx; vb.y += fy; } } } } void solveSolid (final TimeStep step) { // applies extra repulsive force from solid particle groups m_depthBuffer = requestParticleBuffer(m_depthBuffer); float ejectionStrength = step.inv_dt * m_ejectionStrength; for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; if (m_groupBuffer[a] != m_groupBuffer[b]) { float w = contact.weight; Vec2 n = contact.normal; float h = m_depthBuffer[a] + m_depthBuffer[b]; final Vec2 va = m_velocityBuffer.data[a]; final Vec2 vb = m_velocityBuffer.data[b]; final float inter = ejectionStrength * h * w; final float fx = inter * n.x; final float fy = inter * n.y; va.x -= fx; va.y -= fy; vb.x += fx; vb.y += fy; } } } void solveColorMixing (final TimeStep step) { // mixes color between contacting particles m_colorBuffer.data = requestParticleBuffer(ParticleColor.class, m_colorBuffer.data); int colorMixing256 = (int)(256 * m_colorMixingStrength); for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; if ((m_flagsBuffer.data[a] & m_flagsBuffer.data[b] & ParticleType.b2_colorMixingParticle) != 0) { ParticleColor colorA = m_colorBuffer.data[a]; ParticleColor colorB = m_colorBuffer.data[b]; int dr = (colorMixing256 * (colorB.r - colorA.r)) >> 8; int dg = (colorMixing256 * (colorB.g - colorA.g)) >> 8; int db = (colorMixing256 * (colorB.b - colorA.b)) >> 8; int da = (colorMixing256 * (colorB.a - colorA.a)) >> 8; colorA.r += dr; colorA.g += dg; colorA.b += db; colorA.a += da; colorB.r -= dr; colorB.g -= dg; colorB.b -= db; colorB.a -= da; } } } void solveZombie () { // removes particles with zombie flag int newCount = 0; int[] newIndices = new int[m_count]; for (int i = 0; i < m_count; i++) { int flags = m_flagsBuffer.data[i]; if ((flags & ParticleType.b2_zombieParticle) != 0) { ParticleDestructionListener destructionListener = m_world.getParticleDestructionListener(); if ((flags & ParticleType.b2_destructionListener) != 0 && destructionListener != null) { destructionListener.sayGoodbye(i); } newIndices[i] = Settings.invalidParticleIndex; } else { newIndices[i] = newCount; if (i != newCount) { m_flagsBuffer.data[newCount] = m_flagsBuffer.data[i]; m_positionBuffer.data[newCount].set(m_positionBuffer.data[i]); m_velocityBuffer.data[newCount].set(m_velocityBuffer.data[i]); m_groupBuffer[newCount] = m_groupBuffer[i]; if (m_depthBuffer != null) { m_depthBuffer[newCount] = m_depthBuffer[i]; } if (m_colorBuffer.data != null) { m_colorBuffer.data[newCount].set(m_colorBuffer.data[i]); } if (m_userDataBuffer.data != null) { m_userDataBuffer.data[newCount] = m_userDataBuffer.data[i]; } } newCount++; } } // update proxies for (int k = 0; k < m_proxyCount; k++) { Proxy proxy = m_proxyBuffer[k]; proxy.index = newIndices[proxy.index]; } // Proxy lastProxy = std.remove_if( // m_proxyBuffer, m_proxyBuffer + m_proxyCount, // Test.IsProxyInvalid); // m_proxyCount = (int) (lastProxy - m_proxyBuffer); int j = m_proxyCount; for (int i = 0; i < j; i++) { if (Test.IsProxyInvalid(m_proxyBuffer[i])) { --j; Proxy temp = m_proxyBuffer[j]; m_proxyBuffer[j] = m_proxyBuffer[i]; m_proxyBuffer[i] = temp; --i; } } m_proxyCount = j; // update contacts for (int k = 0; k < m_contactCount; k++) { ParticleContact contact = m_contactBuffer[k]; contact.indexA = newIndices[contact.indexA]; contact.indexB = newIndices[contact.indexB]; } // ParticleContact lastContact = std.remove_if( // m_contactBuffer, m_contactBuffer + m_contactCount, // Test.IsContactInvalid); // m_contactCount = (int) (lastContact - m_contactBuffer); j = m_contactCount; for (int i = 0; i < j; i++) { if (Test.IsContactInvalid(m_contactBuffer[i])) { --j; ParticleContact temp = m_contactBuffer[j]; m_contactBuffer[j] = m_contactBuffer[i]; m_contactBuffer[i] = temp; --i; } } m_contactCount = j; // update particle-body contacts for (int k = 0; k < m_bodyContactCount; k++) { ParticleBodyContact contact = m_bodyContactBuffer[k]; contact.index = newIndices[contact.index]; } // ParticleBodyContact lastBodyContact = std.remove_if( // m_bodyContactBuffer, m_bodyContactBuffer + m_bodyContactCount, // Test.IsBodyContactInvalid); // m_bodyContactCount = (int) (lastBodyContact - m_bodyContactBuffer); j = m_bodyContactCount; for (int i = 0; i < j; i++) { if (Test.IsBodyContactInvalid(m_bodyContactBuffer[i])) { --j; ParticleBodyContact temp = m_bodyContactBuffer[j]; m_bodyContactBuffer[j] = m_bodyContactBuffer[i]; m_bodyContactBuffer[i] = temp; --i; } } m_bodyContactCount = j; // update pairs for (int k = 0; k < m_pairCount; k++) { Pair pair = m_pairBuffer[k]; pair.indexA = newIndices[pair.indexA]; pair.indexB = newIndices[pair.indexB]; } // Pair lastPair = std.remove_if(m_pairBuffer, m_pairBuffer + m_pairCount, Test.IsPairInvalid); // m_pairCount = (int) (lastPair - m_pairBuffer); j = m_pairCount; for (int i = 0; i < j; i++) { if (Test.IsPairInvalid(m_pairBuffer[i])) { --j; Pair temp = m_pairBuffer[j]; m_pairBuffer[j] = m_pairBuffer[i]; m_pairBuffer[i] = temp; --i; } } m_pairCount = j; // update triads for (int k = 0; k < m_triadCount; k++) { Triad triad = m_triadBuffer[k]; triad.indexA = newIndices[triad.indexA]; triad.indexB = newIndices[triad.indexB]; triad.indexC = newIndices[triad.indexC]; } // Triad lastTriad = // std.remove_if(m_triadBuffer, m_triadBuffer + m_triadCount, Test.isTriadInvalid); // m_triadCount = (int) (lastTriad - m_triadBuffer); j = m_triadCount; for (int i = 0; i < j; i++) { if (Test.IsTriadInvalid(m_triadBuffer[i])) { --j; Triad temp = m_triadBuffer[j]; m_triadBuffer[j] = m_triadBuffer[i]; m_triadBuffer[i] = temp; --i; } } m_triadCount = j; // update groups for (ParticleGroup group = m_groupList; group != null; group = group.getNext()) { int firstIndex = newCount; int lastIndex = 0; boolean [MASK] = false; for (int i = group.m_firstIndex; i < group.m_lastIndex; i++) { j = newIndices[i]; if (j >= 0) { firstIndex = MathUtils.min(firstIndex, j); lastIndex = MathUtils.max(lastIndex, j + 1); } else { [MASK] = true; } } if (firstIndex < lastIndex) { group.m_firstIndex = firstIndex; group.m_lastIndex = lastIndex; if ( [MASK] ) { if ((group.m_groupFlags & ParticleGroupType.b2_rigidParticleGroup) != 0) { group.m_toBeSplit = true; } } } else { group.m_firstIndex = 0; group.m_lastIndex = 0; if (group.m_destroyAutomatically) { group.m_toBeDestroyed = true; } } } // update particle count m_count = newCount; // m_world.m_stackAllocator.Free(newIndices); // destroy bodies with no particles for (ParticleGroup group = m_groupList; group != null;) { ParticleGroup next = group.getNext(); if (group.m_toBeDestroyed) { destroyParticleGroup(group); } else if (group.m_toBeSplit) { // TODO: split the group } group = next; } } private static class NewIndices { int start, mid, end; final int getIndex (final int i) { if (i < start) { return i; } else if (i < mid) { return i + end - mid; } else if (i < end) { return i + start - mid; } else { return i; } } } private final NewIndices newIndices = new NewIndices(); void RotateBuffer (int start, int mid, int end) { // move the particles assigned to the given group toward the end of array if (start == mid || mid == end) { return; } newIndices.start = start; newIndices.mid = mid; newIndices.end = end; BufferUtils.rotate(m_flagsBuffer.data, start, mid, end); BufferUtils.rotate(m_positionBuffer.data, start, mid, end); BufferUtils.rotate(m_velocityBuffer.data, start, mid, end); BufferUtils.rotate(m_groupBuffer, start, mid, end); if (m_depthBuffer != null) { BufferUtils.rotate(m_depthBuffer, start, mid, end); } if (m_colorBuffer.data != null) { BufferUtils.rotate(m_colorBuffer.data, start, mid, end); } if (m_userDataBuffer.data != null) { BufferUtils.rotate(m_userDataBuffer.data, start, mid, end); } // update proxies for (int k = 0; k < m_proxyCount; k++) { Proxy proxy = m_proxyBuffer[k]; proxy.index = newIndices.getIndex(proxy.index); } // update contacts for (int k = 0; k < m_contactCount; k++) { ParticleContact contact = m_contactBuffer[k]; contact.indexA = newIndices.getIndex(contact.indexA); contact.indexB = newIndices.getIndex(contact.indexB); } // update particle-body contacts for (int k = 0; k < m_bodyContactCount; k++) { ParticleBodyContact contact = m_bodyContactBuffer[k]; contact.index = newIndices.getIndex(contact.index); } // update pairs for (int k = 0; k < m_pairCount; k++) { Pair pair = m_pairBuffer[k]; pair.indexA = newIndices.getIndex(pair.indexA); pair.indexB = newIndices.getIndex(pair.indexB); } // update triads for (int k = 0; k < m_triadCount; k++) { Triad triad = m_triadBuffer[k]; triad.indexA = newIndices.getIndex(triad.indexA); triad.indexB = newIndices.getIndex(triad.indexB); triad.indexC = newIndices.getIndex(triad.indexC); } // update groups for (ParticleGroup group = m_groupList; group != null; group = group.getNext()) { group.m_firstIndex = newIndices.getIndex(group.m_firstIndex); group.m_lastIndex = newIndices.getIndex(group.m_lastIndex - 1) + 1; } } public void setParticleRadius (float radius) { m_particleDiameter = 2 * radius; m_squaredDiameter = m_particleDiameter * m_particleDiameter; m_inverseDiameter = 1 / m_particleDiameter; } public void setParticleDensity (float density) { m_density = density; m_inverseDensity = 1 / m_density; } public float getParticleDensity () { return m_density; } public void setParticleGravityScale (float gravityScale) { m_gravityScale = gravityScale; } public float getParticleGravityScale () { return m_gravityScale; } public void setParticleDamping (float damping) { m_dampingStrength = damping; } public float getParticleDamping () { return m_dampingStrength; } public float getParticleRadius () { return m_particleDiameter / 2; } float getCriticalVelocity (final TimeStep step) { return m_particleDiameter * step.inv_dt; } float getCriticalVelocitySquared (final TimeStep step) { float velocity = getCriticalVelocity(step); return velocity * velocity; } float getCriticalPressure (final TimeStep step) { return m_density * getCriticalVelocitySquared(step); } float getParticleStride () { return Settings.particleStride * m_particleDiameter; } float getParticleMass () { float stride = getParticleStride(); return m_density * stride * stride; } float getParticleInvMass () { return 1.777777f * m_inverseDensity * m_inverseDiameter * m_inverseDiameter; } public int[] getParticleFlagsBuffer () { return m_flagsBuffer.data; } public Vec2[] getParticlePositionBuffer () { return m_positionBuffer.data; } public Vec2[] getParticleVelocityBuffer () { return m_velocityBuffer.data; } public ParticleColor[] getParticleColorBuffer () { m_colorBuffer.data = requestParticleBuffer(ParticleColor.class, m_colorBuffer.data); return m_colorBuffer.data; } public Object[] getParticleUserDataBuffer () { m_userDataBuffer.data = requestParticleBuffer(Object.class, m_userDataBuffer.data); return m_userDataBuffer.data; } public int getParticleMaxCount () { return m_maxCount; } public void setParticleMaxCount (int count) { assert (m_count <= count); m_maxCount = count; } void setParticleBuffer (ParticleBufferInt buffer, int[] newData, int newCapacity) { assert ((newData != null && newCapacity != 0) || (newData == null && newCapacity == 0)); if (buffer.userSuppliedCapacity != 0) { // m_world.m_blockAllocator.Free(buffer.data, sizeof(T) * m_internalAllocatedCapacity); } buffer.data = newData; buffer.userSuppliedCapacity = newCapacity; } void setParticleBuffer (ParticleBuffer buffer, T[] newData, int newCapacity) { assert ((newData != null && newCapacity != 0) || (newData == null && newCapacity == 0)); if (buffer.userSuppliedCapacity != 0) { // m_world.m_blockAllocator.Free(buffer.data, sizeof(T) * m_internalAllocatedCapacity); } buffer.data = newData; buffer.userSuppliedCapacity = newCapacity; } public void setParticleFlagsBuffer (int[] buffer, int capacity) { setParticleBuffer(m_flagsBuffer, buffer, capacity); } public void setParticlePositionBuffer (Vec2[] buffer, int capacity) { setParticleBuffer(m_positionBuffer, buffer, capacity); } public void setParticleVelocityBuffer (Vec2[] buffer, int capacity) { setParticleBuffer(m_velocityBuffer, buffer, capacity); } public void setParticleColorBuffer (ParticleColor[] buffer, int capacity) { setParticleBuffer(m_colorBuffer, buffer, capacity); } public ParticleGroup[] getParticleGroupBuffer () { return m_groupBuffer; } public int getParticleGroupCount () { return m_groupCount; } public ParticleGroup[] getParticleGroupList () { return m_groupBuffer; } public int getParticleCount () { return m_count; } public void setParticleUserDataBuffer (Object[] buffer, int capacity) { setParticleBuffer(m_userDataBuffer, buffer, capacity); } private static final int lowerBound (Proxy[] ray, int length, long tag) { int left = 0; int step, curr; while (length > 0) { step = length / 2; curr = left + step; if (ray[curr].tag < tag) { left = curr + 1; length -= step + 1; } else { length = step; } } return left; } private static final int upperBound (Proxy[] ray, int length, long tag) { int left = 0; int step, curr; while (length > 0) { step = length / 2; curr = left + step; if (ray[curr].tag <= tag) { left = curr + 1; length -= step + 1; } else { length = step; } } return left; } public void queryAABB (ParticleQueryCallback callback, final AABB aabb) { if (m_proxyCount == 0) { return; } final float lowerBoundX = aabb.lowerBound.x; final float lowerBoundY = aabb.lowerBound.y; final float upperBoundX = aabb.upperBound.x; final float upperBoundY = aabb.upperBound.y; int firstProxy = lowerBound(m_proxyBuffer, m_proxyCount, computeTag(m_inverseDiameter * lowerBoundX, m_inverseDiameter * lowerBoundY)); int lastProxy = upperBound(m_proxyBuffer, m_proxyCount, computeTag(m_inverseDiameter * upperBoundX, m_inverseDiameter * upperBoundY)); for (int proxy = firstProxy; proxy < lastProxy; ++proxy) { int i = m_proxyBuffer[proxy].index; final Vec2 p = m_positionBuffer.data[i]; if (lowerBoundX < p.x && p.x < upperBoundX && lowerBoundY < p.y && p.y < upperBoundY) { if (!callback.reportParticle(i)) { break; } } } } /** @param callback * @param point1 * @param point2 */ public void raycast (ParticleRaycastCallback callback, final Vec2 point1, final Vec2 point2) { if (m_proxyCount == 0) { return; } int firstProxy = lowerBound(m_proxyBuffer, m_proxyCount, computeTag( m_inverseDiameter * MathUtils.min(point1.x, point2.x) - 1, m_inverseDiameter * MathUtils.min(point1.y, point2.y) - 1)); int lastProxy = upperBound(m_proxyBuffer, m_proxyCount, computeTag( m_inverseDiameter * MathUtils.max(point1.x, point2.x) + 1, m_inverseDiameter * MathUtils.max(point1.y, point2.y) + 1)); float fraction = 1; // solving the following equation: // ((1-t)*point1+t*point2-position)^2=diameter^2 // where t is a potential fraction final float vx = point2.x - point1.x; final float vy = point2.y - point1.y; float v2 = vx * vx + vy * vy; if (v2 == 0) v2 = Float.MAX_VALUE; for (int proxy = firstProxy; proxy < lastProxy; ++proxy) { int i = m_proxyBuffer[proxy].index; final Vec2 posI = m_positionBuffer.data[i]; final float px = point1.x - posI.x; final float py = point1.y - posI.y; float pv = px * vx + py * vy; float p2 = px * px + py * py; float determinant = pv * pv - v2 * (p2 - m_squaredDiameter); if (determinant >= 0) { float sqrtDeterminant = MathUtils.sqrt(determinant); // find a solution between 0 and fraction float t = (-pv - sqrtDeterminant) / v2; if (t > fraction) { continue; } if (t < 0) { t = (-pv + sqrtDeterminant) / v2; if (t < 0 || t > fraction) { continue; } } final Vec2 n = tempVec; tempVec.x = px + t * vx; tempVec.y = py + t * vy; n.normalize(); final Vec2 point = tempVec2; point.x = point1.x + t * vx; point.y = point1.y + t * vy; float f = callback.reportParticle(i, point, n, t); fraction = MathUtils.min(fraction, f); if (fraction <= 0) { break; } } } } public float computeParticleCollisionEnergy () { float sum_v2 = 0; for (int k = 0; k < m_contactCount; k++) { final ParticleContact contact = m_contactBuffer[k]; int a = contact.indexA; int b = contact.indexB; Vec2 n = contact.normal; final Vec2 va = m_velocityBuffer.data[a]; final Vec2 vb = m_velocityBuffer.data[b]; final float vx = vb.x - va.x; final float vy = vb.y - va.y; float vn = vx * n.x + vy * n.y; if (vn < 0) { sum_v2 += vn * vn; } } return 0.5f * getParticleMass() * sum_v2; } // reallocate a buffer static T[] reallocateBuffer (ParticleBuffer buffer, int oldCapacity, int newCapacity, boolean deferred) { assert (newCapacity > oldCapacity); return BufferUtils.reallocateBuffer(buffer.dataClass, buffer.data, buffer.userSuppliedCapacity, oldCapacity, newCapacity, deferred); } static int[] reallocateBuffer (ParticleBufferInt buffer, int oldCapacity, int newCapacity, boolean deferred) { assert (newCapacity > oldCapacity); return BufferUtils.reallocateBuffer(buffer.data, buffer.userSuppliedCapacity, oldCapacity, newCapacity, deferred); } @SuppressWarnings(""unchecked"") T[] requestParticleBuffer (Class klass, T[] buffer) { if (buffer == null) { buffer = (T[])ArrayReflection.newInstance(klass, m_internalAllocatedCapacity); for (int i = 0; i < m_internalAllocatedCapacity; i++) { try { buffer[i] = ClassReflection.newInstance(klass); } catch (Exception e) { throw new RuntimeException(e); } } } return buffer; } float[] requestParticleBuffer (float[] buffer) { if (buffer == null) { buffer = new float[m_internalAllocatedCapacity]; } return buffer; } public static class ParticleBuffer { public T[] data; final Class dataClass; int userSuppliedCapacity; public ParticleBuffer (Class dataClass) { this.dataClass = dataClass; } } static class ParticleBufferInt { int[] data; int userSuppliedCapacity; } /** Used for detecting particle contacts */ public static class Proxy implements Comparable { int index; long tag; @Override public int compareTo (Proxy o) { return (tag - o.tag) < 0 ? -1 : (o.tag == tag ? 0 : 1); } @Override public boolean equals (Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Proxy other = (Proxy)obj; if (tag != other.tag) return false; return true; } } /** Connection between two particles */ public static class Pair { int indexA, indexB; int flags; float strength; float distance; } /** Connection between three particles */ public static class Triad { int indexA, indexB, indexC; int flags; float strength; final Vec2 pa = new Vec2(), pb = new Vec2(), pc = new Vec2(); float ka, kb, kc, s; } // Callback used with VoronoiDiagram. static class CreateParticleGroupCallback implements VoronoiDiagramCallback { public void callback (int a, int b, int c) { final Vec2 pa = system.m_positionBuffer.data[a]; final Vec2 pb = system.m_positionBuffer.data[b]; final Vec2 pc = system.m_positionBuffer.data[c]; final float dabx = pa.x - pb.x; final float daby = pa.y - pb.y; final float dbcx = pb.x - pc.x; final float dbcy = pb.y - pc.y; final float dcax = pc.x - pa.x; final float dcay = pc.y - pa.y; float maxDistanceSquared = Settings.maxTriadDistanceSquared * system.m_squaredDiameter; if (dabx * dabx + daby * daby < maxDistanceSquared && dbcx * dbcx + dbcy * dbcy < maxDistanceSquared && dcax * dcax + dcay * dcay < maxDistanceSquared) { if (system.m_triadCount >= system.m_triadCapacity) { int oldCapacity = system.m_triadCapacity; int newCapacity = system.m_triadCount != 0 ? 2 * system.m_triadCount : Settings.minParticleBufferCapacity; system.m_triadBuffer = BufferUtils.reallocateBuffer(Triad.class, system.m_triadBuffer, oldCapacity, newCapacity); system.m_triadCapacity = newCapacity; } Triad triad = system.m_triadBuffer[system.m_triadCount]; triad.indexA = a; triad.indexB = b; triad.indexC = c; triad.flags = system.m_flagsBuffer.data[a] | system.m_flagsBuffer.data[b] | system.m_flagsBuffer.data[c]; triad.strength = def.strength; final float midPointx = (float)1 / 3 * (pa.x + pb.x + pc.x); final float midPointy = (float)1 / 3 * (pa.y + pb.y + pc.y); triad.pa.x = pa.x - midPointx; triad.pa.y = pa.y - midPointy; triad.pb.x = pb.x - midPointx; triad.pb.y = pb.y - midPointy; triad.pc.x = pc.x - midPointx; triad.pc.y = pc.y - midPointy; triad.ka = -(dcax * dabx + dcay * daby); triad.kb = -(dabx * dbcx + daby * dbcy); triad.kc = -(dbcx * dcax + dbcy * dcay); triad.s = Vec2.cross(pa, pb) + Vec2.cross(pb, pc) + Vec2.cross(pc, pa); system.m_triadCount++; } } ParticleSystem system; ParticleGroupDef def; // pointer int firstIndex; } // Callback used with VoronoiDiagram. static class JoinParticleGroupsCallback implements VoronoiDiagramCallback { public void callback (int a, int b, int c) { // Create a triad if it will contain particles from both groups. int countA = ((a < groupB.m_firstIndex) ? 1 : 0) + ((b < groupB.m_firstIndex) ? 1 : 0) + ((c < groupB.m_firstIndex) ? 1 : 0); if (countA > 0 && countA < 3) { int af = system.m_flagsBuffer.data[a]; int bf = system.m_flagsBuffer.data[b]; int cf = system.m_flagsBuffer.data[c]; if ((af & bf & cf & k_triadFlags) != 0) { final Vec2 pa = system.m_positionBuffer.data[a]; final Vec2 pb = system.m_positionBuffer.data[b]; final Vec2 pc = system.m_positionBuffer.data[c]; final float dabx = pa.x - pb.x; final float daby = pa.y - pb.y; final float dbcx = pb.x - pc.x; final float dbcy = pb.y - pc.y; final float dcax = pc.x - pa.x; final float dcay = pc.y - pa.y; float maxDistanceSquared = Settings.maxTriadDistanceSquared * system.m_squaredDiameter; if (dabx * dabx + daby * daby < maxDistanceSquared && dbcx * dbcx + dbcy * dbcy < maxDistanceSquared && dcax * dcax + dcay * dcay < maxDistanceSquared) { if (system.m_triadCount >= system.m_triadCapacity) { int oldCapacity = system.m_triadCapacity; int newCapacity = system.m_triadCount != 0 ? 2 * system.m_triadCount : Settings.minParticleBufferCapacity; system.m_triadBuffer = BufferUtils.reallocateBuffer(Triad.class, system.m_triadBuffer, oldCapacity, newCapacity); system.m_triadCapacity = newCapacity; } Triad triad = system.m_triadBuffer[system.m_triadCount]; triad.indexA = a; triad.indexB = b; triad.indexC = c; triad.flags = af | bf | cf; triad.strength = MathUtils.min(groupA.m_strength, groupB.m_strength); final float midPointx = (float)1 / 3 * (pa.x + pb.x + pc.x); final float midPointy = (float)1 / 3 * (pa.y + pb.y + pc.y); triad.pa.x = pa.x - midPointx; triad.pa.y = pa.y - midPointy; triad.pb.x = pb.x - midPointx; triad.pb.y = pb.y - midPointy; triad.pc.x = pc.x - midPointx; triad.pc.y = pc.y - midPointy; triad.ka = -(dcax * dabx + dcay * daby); triad.kb = -(dabx * dbcx + daby * dbcy); triad.kc = -(dbcx * dcax + dbcy * dcay); triad.s = Vec2.cross(pa, pb) + Vec2.cross(pb, pc) + Vec2.cross(pc, pa); system.m_triadCount++; } } } } ParticleSystem system; ParticleGroup groupA; ParticleGroup groupB; }; static class DestroyParticlesInShapeCallback implements ParticleQueryCallback { ParticleSystem system; Shape shape; Transform xf; boolean callDestructionListener; int destroyed; public DestroyParticlesInShapeCallback () { // TODO Auto-generated constructor stub } public void init (ParticleSystem system, Shape shape, Transform xf, boolean callDestructionListener) { this.system = system; this.shape = shape; this.xf = xf; this.destroyed = 0; this.callDestructionListener = callDestructionListener; } @Override public boolean reportParticle (int index) { assert (index >= 0 && index < system.m_count); if (shape.testPoint(xf, system.m_positionBuffer.data[index])) { system.destroyParticle(index, callDestructionListener); destroyed++; } return true; } } static class UpdateBodyContactsCallback implements QueryCallback { ParticleSystem system; private final Vec2 tempVec = new Vec2(); @Override public boolean reportFixture (Fixture fixture) { if (fixture.isSensor()) { return true; } final Shape shape = fixture.getShape(); Body b = fixture.getBody(); Vec2 bp = b.getWorldCenter(); float bm = b.getMass(); float bI = b.getInertia() - bm * b.getLocalCenter().lengthSquared(); float invBm = bm > 0 ? 1 / bm : 0; float invBI = bI > 0 ? 1 / bI : 0; int childCount = shape.getChildCount(); for (int childIndex = 0; childIndex < childCount; childIndex++) { AABB aabb = fixture.getAABB(childIndex); final float aabblowerBoundx = aabb.lowerBound.x - system.m_particleDiameter; final float aabblowerBoundy = aabb.lowerBound.y - system.m_particleDiameter; final float aabbupperBoundx = aabb.upperBound.x + system.m_particleDiameter; final float aabbupperBoundy = aabb.upperBound.y + system.m_particleDiameter; int firstProxy = lowerBound(system.m_proxyBuffer, system.m_proxyCount, computeTag(system.m_inverseDiameter * aabblowerBoundx, system.m_inverseDiameter * aabblowerBoundy)); int lastProxy = upperBound(system.m_proxyBuffer, system.m_proxyCount, computeTag(system.m_inverseDiameter * aabbupperBoundx, system.m_inverseDiameter * aabbupperBoundy)); for (int proxy = firstProxy; proxy != lastProxy; ++proxy) { int a = system.m_proxyBuffer[proxy].index; Vec2 ap = system.m_positionBuffer.data[a]; if (aabblowerBoundx <= ap.x && ap.x <= aabbupperBoundx && aabblowerBoundy <= ap.y && ap.y <= aabbupperBoundy) { float d; final Vec2 n = tempVec; d = fixture.computeDistance(ap, childIndex, n); if (d < system.m_particleDiameter) { float invAm = (system.m_flagsBuffer.data[a] & ParticleType.b2_wallParticle) != 0 ? 0 : system.getParticleInvMass(); final float rpx = ap.x - bp.x; final float rpy = ap.y - bp.y; float rpn = rpx * n.y - rpy * n.x; if (system.m_bodyContactCount >= system.m_bodyContactCapacity) { int oldCapacity = system.m_bodyContactCapacity; int newCapacity = system.m_bodyContactCount != 0 ? 2 * system.m_bodyContactCount : Settings.minParticleBufferCapacity; system.m_bodyContactBuffer = BufferUtils.reallocateBuffer(ParticleBodyContact.class, system.m_bodyContactBuffer, oldCapacity, newCapacity); system.m_bodyContactCapacity = newCapacity; } ParticleBodyContact contact = system.m_bodyContactBuffer[system.m_bodyContactCount]; contact.index = a; contact.body = b; contact.weight = 1 - d * system.m_inverseDiameter; contact.normal.x = -n.x; contact.normal.y = -n.y; contact.mass = 1 / (invAm + invBm + invBI * rpn * rpn); system.m_bodyContactCount++; } } } } return true; } } static class SolveCollisionCallback implements QueryCallback { ParticleSystem system; TimeStep step; private final RayCastInput input = new RayCastInput(); private final RayCastOutput output = new RayCastOutput(); private final Vec2 tempVec = new Vec2(); private final Vec2 tempVec2 = new Vec2(); @Override public boolean reportFixture (Fixture fixture) { if (fixture.isSensor()) { return true; } final Shape shape = fixture.getShape(); Body body = fixture.getBody(); int childCount = shape.getChildCount(); for (int childIndex = 0; childIndex < childCount; childIndex++) { AABB aabb = fixture.getAABB(childIndex); final float aabblowerBoundx = aabb.lowerBound.x - system.m_particleDiameter; final float aabblowerBoundy = aabb.lowerBound.y - system.m_particleDiameter; final float aabbupperBoundx = aabb.upperBound.x + system.m_particleDiameter; final float aabbupperBoundy = aabb.upperBound.y + system.m_particleDiameter; int firstProxy = lowerBound(system.m_proxyBuffer, system.m_proxyCount, computeTag(system.m_inverseDiameter * aabblowerBoundx, system.m_inverseDiameter * aabblowerBoundy)); int lastProxy = upperBound(system.m_proxyBuffer, system.m_proxyCount, computeTag(system.m_inverseDiameter * aabbupperBoundx, system.m_inverseDiameter * aabbupperBoundy)); for (int proxy = firstProxy; proxy != lastProxy; ++proxy) { int a = system.m_proxyBuffer[proxy].index; Vec2 ap = system.m_positionBuffer.data[a]; if (aabblowerBoundx <= ap.x && ap.x <= aabbupperBoundx && aabblowerBoundy <= ap.y && ap.y <= aabbupperBoundy) { Vec2 av = system.m_velocityBuffer.data[a]; final Vec2 temp = tempVec; Transform.mulTransToOutUnsafe(body.m_xf0, ap, temp); Transform.mulToOutUnsafe(body.m_xf, temp, input.p1); input.p2.x = ap.x + step.dt * av.x; input.p2.y = ap.y + step.dt * av.y; input.maxFraction = 1; if (fixture.raycast(output, input, childIndex)) { final Vec2 p = tempVec; p.x = (1 - output.fraction) * input.p1.x + output.fraction * input.p2.x + Settings.linearSlop * output.normal.x; p.y = (1 - output.fraction) * input.p1.y + output.fraction * input.p2.y + Settings.linearSlop * output.normal.y; final float vx = step.inv_dt * (p.x - ap.x); final float vy = step.inv_dt * (p.y - ap.y); av.x = vx; av.y = vy; final float particleMass = system.getParticleMass(); final float ax = particleMass * (av.x - vx); final float ay = particleMass * (av.y - vy); Vec2 b = output.normal; final float fdn = ax * b.x + ay * b.y; final Vec2 f = tempVec2; f.x = fdn * b.x; f.y = fdn * b.y; body.applyLinearImpulse(f, p, true); } } } } return true; } } static class Test { static boolean IsProxyInvalid (final Proxy proxy) { return proxy.index < 0; } static boolean IsContactInvalid (final ParticleContact contact) { return contact.indexA < 0 || contact.indexB < 0; } static boolean IsBodyContactInvalid (final ParticleBodyContact contact) { return contact.index < 0; } static boolean IsPairInvalid (final Pair pair) { return pair.indexA < 0 || pair.indexB < 0; } static boolean IsTriadInvalid (final Triad triad) { return triad.indexA < 0 || triad.indexB < 0 || triad.indexC < 0; } }; } ","modified " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.source.dash; import static java.lang.Math.min; import static java.lang.annotation.ElementType.TYPE_USE; import android.util.Pair; import android.util.SparseArray; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.EmptySampleStream; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream; import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerEmsgCallback; import com.google.android.exoplayer2.source.dash.PlayerEmsgHandler.PlayerTrackEmsgHandler; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.Descriptor; import com.google.android.exoplayer2.source.dash.manifest.EventStream; import com.google.android.exoplayer2.source.dash.manifest.Period; import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.CmcdConfiguration; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.Maps; import com.google.common.primitives.Ints; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.checkerframework.checker.nullness.compatqual.NullableType; /** * A DASH {@link MediaPeriod}. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated /* package */ final class DashMediaPeriod implements MediaPeriod, SequenceableLoader.Callback>, ChunkSampleStream.ReleaseCallback { // Defined by ANSI/SCTE 214-1 2016 7.2.3. private static final Pattern CEA608_SERVICE_DESCRIPTOR_REGEX = Pattern.compile(""CC([1-4])=(.+)""); // Defined by ANSI/SCTE 214-1 2016 7.2.2. private static final Pattern CEA708_SERVICE_DESCRIPTOR_REGEX = Pattern.compile(""([1-4])=lang:(\\w+)(,.+)?""); /* package */ final int id; private final DashChunkSource.Factory chunkSourceFactory; @Nullable private final TransferListener transferListener; @Nullable private final CmcdConfiguration cmcdConfiguration; private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final BaseUrlExclusionList baseUrlExclusionList; private final long elapsedRealtimeOffsetMs; private final LoaderErrorThrower manifestLoaderErrorThrower; private final Allocator allocator; private final TrackGroupArray trackGroups; private final TrackGroupInfo[] trackGroupInfos; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final PlayerEmsgHandler playerEmsgHandler; private final IdentityHashMap, PlayerTrackEmsgHandler> trackEmsgHandlerBySampleStream; private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher; private final DrmSessionEventListener.EventDispatcher drmEventDispatcher; private final PlayerId playerId; @Nullable private Callback callback; private ChunkSampleStream[] sampleStreams; private EventSampleStream[] eventSampleStreams; private SequenceableLoader compositeSequenceableLoader; private DashManifest manifest; private int periodIndex; private List eventStreams; public DashMediaPeriod( int id, DashManifest manifest, BaseUrlExclusionList baseUrlExclusionList, int periodIndex, DashChunkSource.Factory chunkSourceFactory, @Nullable TransferListener transferListener, @Nullable CmcdConfiguration cmcdConfiguration, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, LoadErrorHandlingPolicy loadErrorHandlingPolicy, EventDispatcher mediaSourceEventDispatcher, long elapsedRealtimeOffsetMs, LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, PlayerEmsgCallback playerEmsgCallback, PlayerId playerId) { this.id = id; this.manifest = manifest; this.baseUrlExclusionList = baseUrlExclusionList; this.periodIndex = periodIndex; this.chunkSourceFactory = chunkSourceFactory; this.transferListener = transferListener; this.cmcdConfiguration = cmcdConfiguration; this.drmSessionManager = drmSessionManager; this.drmEventDispatcher = drmEventDispatcher; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.mediaSourceEventDispatcher = mediaSourceEventDispatcher; this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.allocator = allocator; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.playerId = playerId; playerEmsgHandler = new PlayerEmsgHandler(manifest, playerEmsgCallback, allocator); sampleStreams = newSampleStreamArray(0); eventSampleStreams = new EventSampleStream[0]; trackEmsgHandlerBySampleStream = new IdentityHashMap<>(); compositeSequenceableLoader = compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); Period period = manifest.getPeriod(periodIndex); eventStreams = period.eventStreams; Pair result = buildTrackGroups(drmSessionManager, period.adaptationSets, eventStreams); trackGroups = result.first; trackGroupInfos = result.second; } /** * Updates the {@link DashManifest} and the index of this period in the manifest. * * @param manifest The updated manifest. * @param periodIndex the new index of this period in the updated manifest. */ public void updateManifest(DashManifest manifest, int periodIndex) { this.manifest = manifest; this.periodIndex = periodIndex; playerEmsgHandler.updateManifest(manifest); if (sampleStreams != null) { for (ChunkSampleStream sampleStream : sampleStreams) { sampleStream.getChunkSource().updateManifest(manifest, periodIndex); } callback.onContinueLoadingRequested(this); } eventStreams = manifest.getPeriod(periodIndex).eventStreams; for (EventSampleStream eventSampleStream : eventSampleStreams) { for (EventStream eventStream : eventStreams) { if (eventStream.id().equals(eventSampleStream.eventStreamId())) { int lastPeriodIndex = manifest.getPeriodCount() - 1; eventSampleStream.updateEventStream( eventStream, /* eventStreamAppendable= */ manifest.dynamic && periodIndex == lastPeriodIndex); break; } } } } public void release() { playerEmsgHandler.release(); for (ChunkSampleStream sampleStream : sampleStreams) { sampleStream.release(this); } callback = null; } // ChunkSampleStream.ReleaseCallback implementation. @Override public synchronized void onSampleStreamReleased(ChunkSampleStream stream) { PlayerTrackEmsgHandler trackEmsgHandler = trackEmsgHandlerBySampleStream.remove(stream); if (trackEmsgHandler != null) { trackEmsgHandler.release(); } } // MediaPeriod implementation. @Override public void prepare(Callback callback, long positionUs) { this.callback = callback; callback.onPrepared(this); } @Override public void maybeThrowPrepareError() throws IOException { manifestLoaderErrorThrower.maybeThrowError(); } @Override public TrackGroupArray getTrackGroups() { return trackGroups; } @Override public List getStreamKeys(List trackSelections) { List manifestAdaptationSets = manifest.getPeriod(periodIndex).adaptationSets; List streamKeys = new ArrayList<>(); for (ExoTrackSelection trackSelection : trackSelections) { int trackGroupIndex = trackGroups.indexOf(trackSelection.getTrackGroup()); TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; if (trackGroupInfo.trackGroupCategory != TrackGroupInfo.CATEGORY_PRIMARY) { // Ignore non-primary tracks. continue; } int[] adaptationSetIndices = trackGroupInfo.adaptationSetIndices; int[] trackIndices = new int[trackSelection.length()]; for (int i = 0; i < trackSelection.length(); i++) { trackIndices[i] = trackSelection.getIndexInTrackGroup(i); } Arrays.sort(trackIndices); int currentAdaptationSetIndex = 0; int totalTracksInPreviousAdaptationSets = 0; int tracksInCurrentAdaptationSet = manifestAdaptationSets.get(adaptationSetIndices[0]).representations.size(); for (int trackIndex : trackIndices) { while (trackIndex >= totalTracksInPreviousAdaptationSets + tracksInCurrentAdaptationSet) { currentAdaptationSetIndex++; totalTracksInPreviousAdaptationSets += tracksInCurrentAdaptationSet; tracksInCurrentAdaptationSet = manifestAdaptationSets .get(adaptationSetIndices[currentAdaptationSetIndex]) .representations .size(); } streamKeys.add( new StreamKey( periodIndex, adaptationSetIndices[currentAdaptationSetIndex], trackIndex - totalTracksInPreviousAdaptationSets)); } } return streamKeys; } @Override public long selectTracks( @NullableType ExoTrackSelection[] selections, boolean[] mayRetainStreamFlags, @NullableType SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { int[] streamIndexToTrackGroupIndex = getStreamIndexToTrackGroupIndex(selections); releaseDisabledStreams(selections, mayRetainStreamFlags, streams); releaseOrphanEmbeddedStreams(selections, streams, streamIndexToTrackGroupIndex); selectNewStreams( selections, streams, streamResetFlags, positionUs, streamIndexToTrackGroupIndex); ArrayList> sampleStreamList = new ArrayList<>(); ArrayList eventSampleStreamList = new ArrayList<>(); for (SampleStream sampleStream : streams) { if (sampleStream instanceof ChunkSampleStream) { @SuppressWarnings(""unchecked"") ChunkSampleStream stream = (ChunkSampleStream) sampleStream; sampleStreamList.add(stream); } else if (sampleStream instanceof EventSampleStream) { eventSampleStreamList.add((EventSampleStream) sampleStream); } } sampleStreams = newSampleStreamArray(sampleStreamList.size()); sampleStreamList.toArray(sampleStreams); eventSampleStreams = new EventSampleStream[eventSampleStreamList.size()]; eventSampleStreamList.toArray(eventSampleStreams); compositeSequenceableLoader = compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); return positionUs; } @Override public void discardBuffer(long positionUs, boolean toKeyframe) { for (ChunkSampleStream sampleStream : sampleStreams) { sampleStream.discardBuffer(positionUs, toKeyframe); } } @Override public void reevaluateBuffer(long positionUs) { compositeSequenceableLoader.reevaluateBuffer(positionUs); } @Override public boolean continueLoading(long positionUs) { return compositeSequenceableLoader.continueLoading(positionUs); } @Override public boolean isLoading() { return compositeSequenceableLoader.isLoading(); } @Override public long getNextLoadPositionUs() { return compositeSequenceableLoader.getNextLoadPositionUs(); } @Override public long readDiscontinuity() { return C.TIME_UNSET; } @Override public long getBufferedPositionUs() { return compositeSequenceableLoader.getBufferedPositionUs(); } @Override public long seekToUs(long positionUs) { for (ChunkSampleStream sampleStream : sampleStreams) { sampleStream.seekToUs(positionUs); } for (EventSampleStream sampleStream : eventSampleStreams) { sampleStream.seekToUs(positionUs); } return positionUs; } @Override public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) { for (ChunkSampleStream sampleStream : sampleStreams) { if (sampleStream.primaryTrackType == C.TRACK_TYPE_VIDEO) { return sampleStream.getAdjustedSeekPositionUs(positionUs, seekParameters); } } return positionUs; } // SequenceableLoader.Callback implementation. @Override public void onContinueLoadingRequested(ChunkSampleStream sampleStream) { callback.onContinueLoadingRequested(this); } // Internal methods. private int[] getStreamIndexToTrackGroupIndex(ExoTrackSelection[] selections) { int[] streamIndexToTrackGroupIndex = new int[selections.length]; for (int i = 0; i < selections.length; i++) { if (selections[i] != null) { streamIndexToTrackGroupIndex[i] = trackGroups.indexOf(selections[i].getTrackGroup()); } else { streamIndexToTrackGroupIndex[i] = C.INDEX_UNSET; } } return streamIndexToTrackGroupIndex; } private void releaseDisabledStreams( ExoTrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams) { for (int i = 0; i < selections.length; i++) { if (selections[i] == null || !mayRetainStreamFlags[i]) { if (streams[i] instanceof ChunkSampleStream) { @SuppressWarnings(""unchecked"") ChunkSampleStream stream = (ChunkSampleStream) streams[i]; stream.release(this); } else if (streams[i] instanceof EmbeddedSampleStream) { ((EmbeddedSampleStream) streams[i]).release(); } streams[i] = null; } } } private void releaseOrphanEmbeddedStreams( ExoTrackSelection[] selections, SampleStream[] streams, int[] streamIndexToTrackGroupIndex) { for (int i = 0; i < selections.length; i++) { if (streams[i] instanceof EmptySampleStream || streams[i] instanceof EmbeddedSampleStream) { // We need to release an embedded stream if the corresponding primary stream is released. int primaryStreamIndex = getPrimaryStreamIndex(i, streamIndexToTrackGroupIndex); boolean mayRetainStream; if (primaryStreamIndex == C.INDEX_UNSET) { // If the corresponding primary stream is not selected, we may retain an existing // EmptySampleStream. mayRetainStream = streams[i] instanceof EmptySampleStream; } else { // If the corresponding primary stream is selected, we may retain the embedded stream if // the stream's parent still matches. mayRetainStream = (streams[i] instanceof EmbeddedSampleStream) && ((EmbeddedSampleStream) streams[i]).parent == streams[primaryStreamIndex]; } if (!mayRetainStream) { if (streams[i] instanceof EmbeddedSampleStream) { ((EmbeddedSampleStream) streams[i]).release(); } streams[i] = null; } } } } private void selectNewStreams( ExoTrackSelection[] selections, SampleStream[] streams, boolean[] streamResetFlags, long positionUs, int[] streamIndexToTrackGroupIndex) { // Create newly selected primary and event streams. for (int i = 0; i < selections.length; i++) { ExoTrackSelection selection = selections[i]; if (selection == null) { continue; } if (streams[i] == null) { // Create new stream for selection. streamResetFlags[i] = true; int trackGroupIndex = streamIndexToTrackGroupIndex[i]; TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_PRIMARY) { streams[i] = buildSampleStream(trackGroupInfo, selection, positionUs); } else if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_MANIFEST_EVENTS) { EventStream eventStream = eventStreams.get(trackGroupInfo.eventStreamGroupIndex); Format format = selection.getTrackGroup().getFormat(0); streams[i] = new EventSampleStream(eventStream, format, manifest.dynamic); } } else if (streams[i] instanceof ChunkSampleStream) { // Update selection in existing stream. @SuppressWarnings(""unchecked"") ChunkSampleStream stream = (ChunkSampleStream) streams[i]; stream.getChunkSource().updateTrackSelection(selection); } } // Create newly selected embedded streams from the corresponding primary stream. Note that this // second pass is needed because the primary stream may not have been created yet in a first // pass if the index of the primary stream is greater than the index of the embedded stream. for (int i = 0; i < selections.length; i++) { if (streams[i] == null && selections[i] != null) { int trackGroupIndex = streamIndexToTrackGroupIndex[i]; TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_EMBEDDED) { int primaryStreamIndex = getPrimaryStreamIndex(i, streamIndexToTrackGroupIndex); if (primaryStreamIndex == C.INDEX_UNSET) { // If an embedded track is selected without the corresponding primary track, create an // empty sample stream instead. streams[i] = new EmptySampleStream(); } else { streams[i] = ((ChunkSampleStream) streams[primaryStreamIndex]) .selectEmbeddedTrack(positionUs, trackGroupInfo.trackType); } } } } } private int getPrimaryStreamIndex(int embeddedStreamIndex, int[] streamIndexToTrackGroupIndex) { int embeddedTrackGroupIndex = streamIndexToTrackGroupIndex[embeddedStreamIndex]; if (embeddedTrackGroupIndex == C.INDEX_UNSET) { return C.INDEX_UNSET; } int primaryTrackGroupIndex = trackGroupInfos[embeddedTrackGroupIndex].primaryTrackGroupIndex; for (int i = 0; i < streamIndexToTrackGroupIndex.length; i++) { int trackGroupIndex = streamIndexToTrackGroupIndex[i]; if (trackGroupIndex == primaryTrackGroupIndex && trackGroupInfos[trackGroupIndex].trackGroupCategory == TrackGroupInfo.CATEGORY_PRIMARY) { return i; } } return C.INDEX_UNSET; } private static Pair buildTrackGroups( DrmSessionManager drmSessionManager, List adaptationSets, List eventStreams) { int[][] groupedAdaptationSetIndices = getGroupedAdaptationSetIndices(adaptationSets); int primaryGroupCount = groupedAdaptationSetIndices.length; boolean[] primaryGroupHasEventMessageTrackFlags = new boolean[primaryGroupCount]; Format[][] primaryGroupClosedCaptionTrackFormats = new Format[primaryGroupCount][]; int totalEmbeddedTrackGroupCount = identifyEmbeddedTracks( primaryGroupCount, adaptationSets, groupedAdaptationSetIndices, primaryGroupHasEventMessageTrackFlags, primaryGroupClosedCaptionTrackFormats); int totalGroupCount = primaryGroupCount + totalEmbeddedTrackGroupCount + eventStreams.size(); TrackGroup[] trackGroups = new TrackGroup[totalGroupCount]; TrackGroupInfo[] trackGroupInfos = new TrackGroupInfo[totalGroupCount]; int trackGroupCount = buildPrimaryAndEmbeddedTrackGroupInfos( drmSessionManager, adaptationSets, groupedAdaptationSetIndices, primaryGroupCount, primaryGroupHasEventMessageTrackFlags, primaryGroupClosedCaptionTrackFormats, trackGroups, trackGroupInfos); buildManifestEventTrackGroupInfos(eventStreams, trackGroups, trackGroupInfos, trackGroupCount); return Pair.create(new TrackGroupArray(trackGroups), trackGroupInfos); } /** * Groups adaptation sets. Two adaptations sets belong to the same group if either: * *
    *
  • One is a trick-play adaptation set and uses a {@code * http://dashif.org/guidelines/trickmode} essential or supplemental property to indicate * that the other is the main adaptation set to which it corresponds. *
  • The two adaptation sets are marked as safe for switching using {@code * urn:mpeg:dash:adaptation-set-switching:2016} supplemental properties. *
* * @param adaptationSets The adaptation sets to merge. * @return An array of groups, where each group is an array of adaptation set indices. */ private static int[][] getGroupedAdaptationSetIndices(List adaptationSets) { int adaptationSetCount = adaptationSets.size(); HashMap adaptationSetIdToIndex = Maps.newHashMapWithExpectedSize(adaptationSetCount); List> adaptationSetGroupedIndices = new ArrayList<>(adaptationSetCount); SparseArray> adaptationSetIndexToGroupedIndices = new SparseArray<>(adaptationSetCount); // Initially make each adaptation set belong to its own group. Also build the // adaptationSetIdToIndex map. for (int i = 0; i < adaptationSetCount; i++) { adaptationSetIdToIndex.put(adaptationSets.get(i).id, i); List initialGroup = new ArrayList<>(); initialGroup.add(i); adaptationSetGroupedIndices.add(initialGroup); adaptationSetIndexToGroupedIndices.put(i, initialGroup); } // Merge adaptation set groups. for (int i = 0; i < adaptationSetCount; i++) { int mergedGroupIndex = i; AdaptationSet adaptationSet = adaptationSets.get(i); // Trick-play adaptation sets are merged with their corresponding main adaptation sets. @Nullable Descriptor trickPlayProperty = findTrickPlayProperty(adaptationSet.essentialProperties); if (trickPlayProperty == null) { // Trick-play can also be specified using a supplemental property. trickPlayProperty = findTrickPlayProperty(adaptationSet.supplementalProperties); } if (trickPlayProperty != null) { long mainAdaptationSetId = Long.parseLong(trickPlayProperty.value); @Nullable Integer mainAdaptationSetIndex = adaptationSetIdToIndex.get(mainAdaptationSetId); if (mainAdaptationSetIndex != null) { mergedGroupIndex = mainAdaptationSetIndex; } } // Adaptation sets that are safe for switching are merged, using the smallest index for the // merged group. if (mergedGroupIndex == i) { @Nullable Descriptor adaptationSetSwitchingProperty = findAdaptationSetSwitchingProperty(adaptationSet.supplementalProperties); if (adaptationSetSwitchingProperty != null) { String[] otherAdaptationSetIds = Util.split(adaptationSetSwitchingProperty.value, "",""); for (String adaptationSetId : otherAdaptationSetIds) { @Nullable Integer otherAdaptationSetIndex = adaptationSetIdToIndex.get(Long.parseLong(adaptationSetId)); if (otherAdaptationSetIndex != null) { mergedGroupIndex = min(mergedGroupIndex, otherAdaptationSetIndex); } } } } // Merge the groups if necessary. if (mergedGroupIndex != i) { List thisGroup = adaptationSetIndexToGroupedIndices.get(i); List mergedGroup = adaptationSetIndexToGroupedIndices.get(mergedGroupIndex); mergedGroup.addAll(thisGroup); adaptationSetIndexToGroupedIndices.put(i, mergedGroup); adaptationSetGroupedIndices.remove(thisGroup); } } int[][] groupedAdaptationSetIndices = new int[adaptationSetGroupedIndices.size()][]; for (int i = 0; i < groupedAdaptationSetIndices.length; i++) { groupedAdaptationSetIndices[i] = Ints.toArray(adaptationSetGroupedIndices.get(i)); // Restore the original adaptation set order within each group. Arrays.sort(groupedAdaptationSetIndices[i]); } return groupedAdaptationSetIndices; } /** * Iterates through list of primary track groups and identifies embedded tracks. * * @param primaryGroupCount The number of primary track groups. * @param adaptationSets The list of {@link AdaptationSet} of the current DASH period. * @param groupedAdaptationSetIndices The indices of {@link AdaptationSet} that belongs to the * same primary group, grouped in primary track groups order. * @param primaryGroupHasEventMessageTrackFlags An output array to be filled with flags indicating * whether each of the primary track groups contains an embedded event message track. * @param primaryGroupClosedCaptionTrackFormats An output array to be filled with track formats * for closed caption tracks embedded in each of the primary track groups. * @return Total number of embedded track groups. */ private static int identifyEmbeddedTracks( int primaryGroupCount, List adaptationSets, int[][] groupedAdaptationSetIndices, boolean[] primaryGroupHasEventMessageTrackFlags, Format[][] primaryGroupClosedCaptionTrackFormats) { int numEmbeddedTrackGroups = 0; for (int i = 0; i < primaryGroupCount; i++) { if (hasEventMessageTrack(adaptationSets, groupedAdaptationSetIndices[i])) { primaryGroupHasEventMessageTrackFlags[i] = true; numEmbeddedTrackGroups++; } primaryGroupClosedCaptionTrackFormats[i] = getClosedCaptionTrackFormats(adaptationSets, groupedAdaptationSetIndices[i]); if (primaryGroupClosedCaptionTrackFormats[i].length != 0) { numEmbeddedTrackGroups++; } } return numEmbeddedTrackGroups; } private static int buildPrimaryAndEmbeddedTrackGroupInfos( DrmSessionManager drmSessionManager, List adaptationSets, int[][] groupedAdaptationSetIndices, int primaryGroupCount, boolean[] primaryGroupHasEventMessageTrackFlags, Format[][] primaryGroupClosedCaptionTrackFormats, TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos) { int trackGroupCount = 0; for (int i = 0; i < primaryGroupCount; i++) { int[] adaptationSetIndices = groupedAdaptationSetIndices[i]; List representations = new ArrayList<>(); for (int adaptationSetIndex : adaptationSetIndices) { representations.addAll(adaptationSets.get(adaptationSetIndex).representations); } Format[] formats = new Format[representations.size()]; for (int j = 0; j < formats.length; j++) { Format format = representations.get(j).format; formats[j] = format.copyWithCryptoType(drmSessionManager.getCryptoType(format)); } AdaptationSet firstAdaptationSet = adaptationSets.get(adaptationSetIndices[0]); String trackGroupId = firstAdaptationSet.id != AdaptationSet.ID_UNSET ? Long.toString(firstAdaptationSet.id) : (""unset:"" + i); int primaryTrackGroupIndex = trackGroupCount++; int eventMessageTrackGroupIndex = primaryGroupHasEventMessageTrackFlags[i] ? trackGroupCount++ : C.INDEX_UNSET; int closedCaptionTrackGroupIndex = primaryGroupClosedCaptionTrackFormats[i].length != 0 ? trackGroupCount++ : C.INDEX_UNSET; trackGroups[primaryTrackGroupIndex] = new TrackGroup(trackGroupId, formats); trackGroupInfos[primaryTrackGroupIndex] = TrackGroupInfo.primaryTrack( firstAdaptationSet.type, adaptationSetIndices, primaryTrackGroupIndex, eventMessageTrackGroupIndex, closedCaptionTrackGroupIndex); if (eventMessageTrackGroupIndex != C.INDEX_UNSET) { String eventMessageTrackGroupId = trackGroupId + "":emsg""; Format format = new Format.Builder() .setId(eventMessageTrackGroupId) .setSampleMimeType(MimeTypes.APPLICATION_EMSG) .build(); trackGroups[eventMessageTrackGroupIndex] = new TrackGroup(eventMessageTrackGroupId, format); trackGroupInfos[eventMessageTrackGroupIndex] = TrackGroupInfo.embeddedEmsgTrack(adaptationSetIndices, primaryTrackGroupIndex); } if (closedCaptionTrackGroupIndex != C.INDEX_UNSET) { String closedCaptionTrackGroupId = trackGroupId + "":cc""; trackGroups[closedCaptionTrackGroupIndex] = new TrackGroup(closedCaptionTrackGroupId, primaryGroupClosedCaptionTrackFormats[i]); trackGroupInfos[closedCaptionTrackGroupIndex] = TrackGroupInfo.embeddedClosedCaptionTrack(adaptationSetIndices, primaryTrackGroupIndex); } } return trackGroupCount; } private static void buildManifestEventTrackGroupInfos( List eventStreams, TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos, int existingTrackGroupCount) { for (int i = 0; i < eventStreams.size(); i++) { EventStream eventStream = eventStreams.get(i); Format format = new Format.Builder() .setId(eventStream.id()) .setSampleMimeType(MimeTypes.APPLICATION_EMSG) .build(); String uniqueTrackGroupId = eventStream.id() + "":"" + i; trackGroups[existingTrackGroupCount] = new TrackGroup(uniqueTrackGroupId, format); trackGroupInfos[existingTrackGroupCount++] = TrackGroupInfo.mpdEventTrack(i); } } private ChunkSampleStream buildSampleStream( TrackGroupInfo trackGroupInfo, ExoTrackSelection selection, long positionUs) { int embeddedTrackCount = 0; boolean enableEventMessageTrack = trackGroupInfo.embeddedEventMessageTrackGroupIndex != C.INDEX_UNSET; TrackGroup embeddedEventMessageTrackGroup = null; if (enableEventMessageTrack) { embeddedEventMessageTrackGroup = trackGroups.get(trackGroupInfo.embeddedEventMessageTrackGroupIndex); embeddedTrackCount++; } boolean enableClosedCaptionTrack = trackGroupInfo.embeddedClosedCaptionTrackGroupIndex != C.INDEX_UNSET; TrackGroup embeddedClosedCaptionTrackGroup = null; if (enableClosedCaptionTrack) { embeddedClosedCaptionTrackGroup = trackGroups.get(trackGroupInfo.embeddedClosedCaptionTrackGroupIndex); embeddedTrackCount += embeddedClosedCaptionTrackGroup.length; } Format[] embeddedTrackFormats = new Format[embeddedTrackCount]; int[] embeddedTrackTypes = new int[embeddedTrackCount]; embeddedTrackCount = 0; if (enableEventMessageTrack) { embeddedTrackFormats[embeddedTrackCount] = embeddedEventMessageTrackGroup.getFormat(0); embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_METADATA; embeddedTrackCount++; } List embeddedClosedCaptionTrackFormats = new ArrayList<>(); if (enableClosedCaptionTrack) { for (int i = 0; i < embeddedClosedCaptionTrackGroup.length; i++) { embeddedTrackFormats[embeddedTrackCount] = embeddedClosedCaptionTrackGroup.getFormat(i); embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_TEXT; embeddedClosedCaptionTrackFormats.add(embeddedTrackFormats[embeddedTrackCount]); embeddedTrackCount++; } } PlayerTrackEmsgHandler trackPlayerEmsgHandler = manifest.dynamic && enableEventMessageTrack ? playerEmsgHandler.newPlayerTrackEmsgHandler() : null; DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( manifestLoaderErrorThrower, manifest, baseUrlExclusionList, periodIndex, trackGroupInfo.adaptationSetIndices, selection, trackGroupInfo.trackType, elapsedRealtimeOffsetMs, enableEventMessageTrack, embeddedClosedCaptionTrackFormats, trackPlayerEmsgHandler, transferListener, playerId, cmcdConfiguration); ChunkSampleStream stream = new ChunkSampleStream<>( trackGroupInfo.trackType, embeddedTrackTypes, embeddedTrackFormats, chunkSource, this, allocator, positionUs, drmSessionManager, drmEventDispatcher, loadErrorHandlingPolicy, mediaSourceEventDispatcher); synchronized (this) { // The map is also accessed on the loading thread so synchronize access. trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler); } return stream; } @Nullable private static Descriptor findAdaptationSetSwitchingProperty(List descriptors) { return findDescriptor(descriptors, ""urn:mpeg:dash:adaptation-set-switching:2016""); } @Nullable private static Descriptor findTrickPlayProperty(List descriptors) { return findDescriptor(descriptors, ""http://dashif.org/guidelines/trickmode""); } @Nullable private static Descriptor findDescriptor(List descriptors, String schemeIdUri) { for (int i = 0; i < descriptors.size(); i++) { Descriptor descriptor = descriptors.get(i); if (schemeIdUri.equals(descriptor.schemeIdUri)) { return descriptor; } } return null; } private static boolean hasEventMessageTrack( List adaptationSets, int[] adaptationSetIndices) { for (int i : adaptationSetIndices) { List representations = adaptationSets.get(i).representations; for (int j = 0; j < representations.size(); j++) { Representation representation = representations.get(j); if (!representation.inbandEventStreams.isEmpty()) { return true; } } } return false; } private static Format[] getClosedCaptionTrackFormats( List adaptationSets, int[] adaptationSetIndices) { for (int i : adaptationSetIndices) { AdaptationSet adaptationSet = adaptationSets.get(i); List descriptors = adaptationSets.get(i).accessibilityDescriptors; for (int j = 0; j < descriptors.size(); j++) { Descriptor descriptor = descriptors.get(j); if (""urn:scte:dash:cc:cea-608:2015"".equals(descriptor.schemeIdUri)) { Format cea608Format = new Format.Builder() .setSampleMimeType(MimeTypes.APPLICATION_CEA608) .setId(adaptationSet.id + "":cea608"") .build(); return parseClosedCaptionDescriptor( descriptor, CEA608_SERVICE_DESCRIPTOR_REGEX, cea608Format); } else if (""urn:scte:dash:cc:cea-708:2015"".equals(descriptor.schemeIdUri)) { Format [MASK] = new Format.Builder() .setSampleMimeType(MimeTypes.APPLICATION_CEA708) .setId(adaptationSet.id + "":cea708"") .build(); return parseClosedCaptionDescriptor( descriptor, CEA708_SERVICE_DESCRIPTOR_REGEX, [MASK] ); } } } return new Format[0]; } private static Format[] parseClosedCaptionDescriptor( Descriptor descriptor, Pattern serviceDescriptorRegex, Format baseFormat) { @Nullable String value = descriptor.value; if (value == null) { // There are embedded closed caption tracks, but service information is not declared. return new Format[] {baseFormat}; } String[] services = Util.split(value, "";""); Format[] formats = new Format[services.length]; for (int i = 0; i < services.length; i++) { Matcher matcher = serviceDescriptorRegex.matcher(services[i]); if (!matcher.matches()) { // If we can't parse service information for all services, assume a single track. return new Format[] {baseFormat}; } int accessibilityChannel = Integer.parseInt(matcher.group(1)); formats[i] = baseFormat .buildUpon() .setId(baseFormat.id + "":"" + accessibilityChannel) .setAccessibilityChannel(accessibilityChannel) .setLanguage(matcher.group(2)) .build(); } return formats; } // We won't assign the array to a variable that erases the generic type, and then write into it. @SuppressWarnings({""unchecked"", ""rawtypes""}) private static ChunkSampleStream[] newSampleStreamArray(int length) { return new ChunkSampleStream[length]; } private static final class TrackGroupInfo { @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({CATEGORY_PRIMARY, CATEGORY_EMBEDDED, CATEGORY_MANIFEST_EVENTS}) public @interface TrackGroupCategory {} /** * A normal track group that has its samples drawn from the stream. For example: a video Track * Group or an audio Track Group. */ private static final int CATEGORY_PRIMARY = 0; /** * A track group whose samples are embedded within one of the primary streams. For example: an * EMSG track has its sample embedded in emsg atoms in one of the primary streams. */ private static final int CATEGORY_EMBEDDED = 1; /** * A track group that has its samples listed explicitly in the DASH manifest file. For example: * an EventStream track has its sample (Events) included directly in the DASH manifest file. */ private static final int CATEGORY_MANIFEST_EVENTS = 2; public final int[] adaptationSetIndices; public final @C.TrackType int trackType; public final @TrackGroupCategory int trackGroupCategory; public final int eventStreamGroupIndex; public final int primaryTrackGroupIndex; public final int embeddedEventMessageTrackGroupIndex; public final int embeddedClosedCaptionTrackGroupIndex; public static TrackGroupInfo primaryTrack( int trackType, int[] adaptationSetIndices, int primaryTrackGroupIndex, int embeddedEventMessageTrackGroupIndex, int embeddedClosedCaptionTrackGroupIndex) { return new TrackGroupInfo( trackType, CATEGORY_PRIMARY, adaptationSetIndices, primaryTrackGroupIndex, embeddedEventMessageTrackGroupIndex, embeddedClosedCaptionTrackGroupIndex, /* eventStreamGroupIndex= */ -1); } public static TrackGroupInfo embeddedEmsgTrack( int[] adaptationSetIndices, int primaryTrackGroupIndex) { return new TrackGroupInfo( C.TRACK_TYPE_METADATA, CATEGORY_EMBEDDED, adaptationSetIndices, primaryTrackGroupIndex, C.INDEX_UNSET, C.INDEX_UNSET, /* eventStreamGroupIndex= */ -1); } public static TrackGroupInfo embeddedClosedCaptionTrack( int[] adaptationSetIndices, int primaryTrackGroupIndex) { return new TrackGroupInfo( C.TRACK_TYPE_TEXT, CATEGORY_EMBEDDED, adaptationSetIndices, primaryTrackGroupIndex, C.INDEX_UNSET, C.INDEX_UNSET, /* eventStreamGroupIndex= */ -1); } public static TrackGroupInfo mpdEventTrack(int eventStreamIndex) { return new TrackGroupInfo( C.TRACK_TYPE_METADATA, CATEGORY_MANIFEST_EVENTS, new int[0], /* primaryTrackGroupIndex= */ -1, C.INDEX_UNSET, C.INDEX_UNSET, eventStreamIndex); } private TrackGroupInfo( @C.TrackType int trackType, @TrackGroupCategory int trackGroupCategory, int[] adaptationSetIndices, int primaryTrackGroupIndex, int embeddedEventMessageTrackGroupIndex, int embeddedClosedCaptionTrackGroupIndex, int eventStreamGroupIndex) { this.trackType = trackType; this.adaptationSetIndices = adaptationSetIndices; this.trackGroupCategory = trackGroupCategory; this.primaryTrackGroupIndex = primaryTrackGroupIndex; this.embeddedEventMessageTrackGroupIndex = embeddedEventMessageTrackGroupIndex; this.embeddedClosedCaptionTrackGroupIndex = embeddedClosedCaptionTrackGroupIndex; this.eventStreamGroupIndex = eventStreamGroupIndex; } } } ","cea708Format " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.metadata.id3; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataInputBuffer; import com.google.android.exoplayer2.metadata.SimpleMetadataDecoder; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Ascii; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; /** * Decodes ID3 tags. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public final class Id3Decoder extends SimpleMetadataDecoder { /** A predicate for determining whether individual frames should be decoded. */ public interface FramePredicate { /** * Returns whether a frame with the specified parameters should be decoded. * * @param majorVersion The major version of the ID3 tag. * @param id0 The first byte of the frame ID. * @param id1 The second byte of the frame ID. * @param id2 The third byte of the frame ID. * @param id3 The fourth byte of the frame ID. * @return Whether the frame should be decoded. */ boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3); } /** A predicate that indicates no frames should be decoded. */ public static final FramePredicate NO_FRAMES_PREDICATE = (majorVersion, id0, id1, id2, id3) -> false; private static final String TAG = ""Id3Decoder""; /** The first three bytes of a well formed ID3 tag header. */ public static final int ID3_TAG = 0x00494433; /** Length of an ID3 tag header. */ public static final int ID3_HEADER_LENGTH = 10; private static final int FRAME_FLAG_V3_IS_COMPRESSED = 0x0080; private static final int FRAME_FLAG_V3_IS_ENCRYPTED = 0x0040; private static final int FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER = 0x0020; private static final int FRAME_FLAG_V4_IS_COMPRESSED = 0x0008; private static final int FRAME_FLAG_V4_IS_ENCRYPTED = 0x0004; private static final int FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER = 0x0040; private static final int FRAME_FLAG_V4_IS_UNSYNCHRONIZED = 0x0002; private static final int FRAME_FLAG_V4_HAS_DATA_LENGTH = 0x0001; private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0; private static final int ID3_TEXT_ENCODING_UTF_16 = 1; private static final int ID3_TEXT_ENCODING_UTF_16BE = 2; private static final int ID3_TEXT_ENCODING_UTF_8 = 3; @Nullable private final FramePredicate framePredicate; public Id3Decoder() { this(null); } /** * @param framePredicate Determines which frames are decoded. May be null to decode all frames. */ public Id3Decoder(@Nullable FramePredicate framePredicate) { this.framePredicate = framePredicate; } @Override @Nullable @SuppressWarnings(""ByteBufferBackingArray"") // Buffer validated by SimpleMetadataDecoder.decode protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) { return decode(buffer.array(), buffer.limit()); } /** * Decodes ID3 tags. * * @param data The bytes to decode ID3 tags from. * @param size Amount of bytes in {@code data} to read. * @return A {@link Metadata} object containing the decoded ID3 tags, or null if the data could * not be decoded. */ @Nullable public Metadata decode(byte[] data, int size) { List id3Frames = new ArrayList<>(); ParsableByteArray id3Data = new ParsableByteArray(data, size); @Nullable Id3Header id3Header = decodeHeader(id3Data); if (id3Header == null) { return null; } int startPosition = id3Data.getPosition(); int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10; int framesSize = id3Header.framesSize; if (id3Header.isUnsynchronized) { framesSize = removeUnsynchronization(id3Data, id3Header.framesSize); } id3Data.setLimit(startPosition + framesSize); boolean unsignedIntFrameSizeHack = false; if (!validateFrames(id3Data, id3Header.majorVersion, frameHeaderSize, false)) { if (id3Header.majorVersion == 4 && validateFrames(id3Data, 4, frameHeaderSize, true)) { unsignedIntFrameSizeHack = true; } else { Log.w(TAG, ""Failed to validate ID3 tag with majorVersion="" + id3Header.majorVersion); return null; } } while (id3Data.bytesLeft() >= frameHeaderSize) { @Nullable Id3Frame frame = decodeFrame( id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); if (frame != null) { id3Frames.add(frame); } } return new Metadata(id3Frames); } /** * @param data A {@link ParsableByteArray} from which the header should be read. * @return The parsed header, or null if the ID3 tag is unsupported. */ @Nullable private static Id3Header decodeHeader(ParsableByteArray data) { if (data.bytesLeft() < ID3_HEADER_LENGTH) { Log.w(TAG, ""Data too short to be an ID3 tag""); return null; } int id = data.readUnsignedInt24(); if (id != ID3_TAG) { Log.w(TAG, ""Unexpected first three bytes of ID3 tag header: 0x"" + String.format(""%06X"", id)); return null; } int majorVersion = data.readUnsignedByte(); data.skipBytes(1); // Skip minor version. int flags = data.readUnsignedByte(); int framesSize = data.readSynchSafeInt(); if (majorVersion == 2) { boolean isCompressed = (flags & 0x40) != 0; if (isCompressed) { Log.w(TAG, ""Skipped ID3 tag with majorVersion=2 and undefined compression scheme""); return null; } } else if (majorVersion == 3) { boolean hasExtendedHeader = (flags & 0x40) != 0; if (hasExtendedHeader) { int extendedHeaderSize = data.readInt(); // Size excluding size field. data.skipBytes(extendedHeaderSize); framesSize -= (extendedHeaderSize + 4); } } else if (majorVersion == 4) { boolean hasExtendedHeader = (flags & 0x40) != 0; if (hasExtendedHeader) { int extendedHeaderSize = data.readSynchSafeInt(); // Size including size field. data.skipBytes(extendedHeaderSize - 4); framesSize -= extendedHeaderSize; } boolean hasFooter = (flags & 0x10) != 0; if (hasFooter) { framesSize -= 10; } } else { Log.w(TAG, ""Skipped ID3 tag with unsupported majorVersion="" + majorVersion); return null; } // isUnsynchronized is advisory only in version 4. Frame level flags are used instead. boolean isUnsynchronized = majorVersion < 4 && (flags & 0x80) != 0; return new Id3Header(majorVersion, isUnsynchronized, framesSize); } private static boolean validateFrames( ParsableByteArray id3Data, int majorVersion, int frameHeaderSize, boolean unsignedIntFrameSizeHack) { int startPosition = id3Data.getPosition(); try { while (id3Data.bytesLeft() >= frameHeaderSize) { // Read the next frame header. int id; long frameSize; int flags; if (majorVersion >= 3) { id = id3Data.readInt(); frameSize = id3Data.readUnsignedInt(); flags = id3Data.readUnsignedShort(); } else { id = id3Data.readUnsignedInt24(); frameSize = id3Data.readUnsignedInt24(); flags = 0; } // Validate the frame header and skip to the next one. if (id == 0 && frameSize == 0 && flags == 0) { // We've reached zero padding after the end of the final frame. return true; } else { if (majorVersion == 4 && !unsignedIntFrameSizeHack) { // Parse the data size as a synchsafe integer, as per the spec. if ((frameSize & 0x808080L) != 0) { return false; } frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7) | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21); } boolean hasGroupIdentifier = false; boolean hasDataLength = false; if (majorVersion == 4) { hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0; hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0; } else if (majorVersion == 3) { hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0; // A V3 frame has data length if and only if it's compressed. hasDataLength = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0; } int minimumFrameSize = 0; if (hasGroupIdentifier) { minimumFrameSize++; } if (hasDataLength) { minimumFrameSize += 4; } if (frameSize < minimumFrameSize) { return false; } if (id3Data.bytesLeft() < frameSize) { return false; } id3Data.skipBytes((int) frameSize); // flags } } return true; } finally { id3Data.setPosition(startPosition); } } @Nullable private static Id3Frame decodeFrame( int majorVersion, ParsableByteArray id3Data, boolean unsignedIntFrameSizeHack, int frameHeaderSize, @Nullable FramePredicate framePredicate) { int frameId0 = id3Data.readUnsignedByte(); int frameId1 = id3Data.readUnsignedByte(); int frameId2 = id3Data.readUnsignedByte(); int frameId3 = majorVersion >= 3 ? id3Data.readUnsignedByte() : 0; int frameSize; if (majorVersion == 4) { frameSize = id3Data.readUnsignedIntToInt(); if (!unsignedIntFrameSizeHack) { frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7) | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21); } } else if (majorVersion == 3) { frameSize = id3Data.readUnsignedIntToInt(); } else /* id3Header.majorVersion == 2 */ { frameSize = id3Data.readUnsignedInt24(); } int flags = majorVersion >= 3 ? id3Data.readUnsignedShort() : 0; if (frameId0 == 0 && frameId1 == 0 && frameId2 == 0 && frameId3 == 0 && frameSize == 0 && flags == 0) { // We must be reading zero padding at the end of the tag. id3Data.setPosition(id3Data.limit()); return null; } int nextFramePosition = id3Data.getPosition() + frameSize; if (nextFramePosition > id3Data.limit()) { Log.w(TAG, ""Frame size exceeds remaining tag data""); id3Data.setPosition(id3Data.limit()); return null; } if (framePredicate != null && !framePredicate.evaluate(majorVersion, frameId0, frameId1, frameId2, frameId3)) { // Filtered by the predicate. id3Data.setPosition(nextFramePosition); return null; } // Frame flags. boolean isCompressed = false; boolean isEncrypted = false; boolean isUnsynchronized = false; boolean hasDataLength = false; boolean hasGroupIdentifier = false; if (majorVersion == 3) { isCompressed = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0; isEncrypted = (flags & FRAME_FLAG_V3_IS_ENCRYPTED) != 0; hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0; // A V3 frame has data length if and only if it's compressed. hasDataLength = isCompressed; } else if (majorVersion == 4) { hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0; isCompressed = (flags & FRAME_FLAG_V4_IS_COMPRESSED) != 0; isEncrypted = (flags & FRAME_FLAG_V4_IS_ENCRYPTED) != 0; isUnsynchronized = (flags & FRAME_FLAG_V4_IS_UNSYNCHRONIZED) != 0; hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0; } if (isCompressed || isEncrypted) { Log.w(TAG, ""Skipping unsupported compressed or encrypted frame""); id3Data.setPosition(nextFramePosition); return null; } if (hasGroupIdentifier) { frameSize--; id3Data.skipBytes(1); } if (hasDataLength) { frameSize -= 4; id3Data.skipBytes(4); } if (isUnsynchronized) { frameSize = removeUnsynchronization(id3Data, frameSize); } try { Id3Frame frame; if (frameId0 == 'T' && frameId1 == 'X' && frameId2 == 'X' && (majorVersion == 2 || frameId3 == 'X')) { frame = decodeTxxxFrame(id3Data, frameSize); } else if (frameId0 == 'T') { String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeTextInformationFrame(id3Data, frameSize, id); } else if (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X' && (majorVersion == 2 || frameId3 == 'X')) { frame = decodeWxxxFrame(id3Data, frameSize); } else if (frameId0 == 'W') { String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeUrlLinkFrame(id3Data, frameSize, id); } else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') { frame = decodePrivFrame(id3Data, frameSize); } else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' && (frameId3 == 'B' || majorVersion == 2)) { frame = decodeGeobFrame(id3Data, frameSize); } else if (majorVersion == 2 ? (frameId0 == 'P' && frameId1 == 'I' && frameId2 == 'C') : (frameId0 == 'A' && frameId1 == 'P' && frameId2 == 'I' && frameId3 == 'C')) { frame = decodeApicFrame(id3Data, frameSize, majorVersion); } else if (frameId0 == 'C' && frameId1 == 'O' && frameId2 == 'M' && (frameId3 == 'M' || majorVersion == 2)) { frame = decodeCommentFrame(id3Data, frameSize); } else if (frameId0 == 'C' && frameId1 == 'H' && frameId2 == 'A' && frameId3 == 'P') { frame = decodeChapterFrame( id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') { frame = decodeChapterTOCFrame( id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); } else if (frameId0 == 'M' && frameId1 == 'L' && frameId2 == 'L' && frameId3 == 'T') { frame = decodeMlltFrame(id3Data, frameSize); } else { String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeBinaryFrame(id3Data, frameSize, id); } if (frame == null) { Log.w( TAG, ""Failed to decode frame: id="" + getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3) + "", frameSize="" + frameSize); } return frame; } finally { id3Data.setPosition(nextFramePosition); } } @Nullable private static TextInformationFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) { if (frameSize < 1) { // Frame is malformed. return null; } int encoding = id3Data.readUnsignedByte(); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, getCharset(encoding)); ImmutableList values = decodeTextInformationFrameValues( data, encoding, descriptionEndIndex + delimiterLength(encoding)); return new TextInformationFrame(""TXXX"", description, values); } @Nullable private static TextInformationFrame decodeTextInformationFrame( ParsableByteArray id3Data, int frameSize, String id) { if (frameSize < 1) { // Frame is malformed. return null; } int encoding = id3Data.readUnsignedByte(); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); ImmutableList values = decodeTextInformationFrameValues(data, encoding, 0); return new TextInformationFrame(id, null, values); } private static ImmutableList decodeTextInformationFrameValues( byte[] data, final int encoding, final int index) { if (index >= data.length) { return ImmutableList.of(""""); } ImmutableList.Builder values = ImmutableList.builder(); int valueStartIndex = index; int valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); while (valueStartIndex < valueEndIndex) { String value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, getCharset(encoding)); values.add(value); valueStartIndex = valueEndIndex + delimiterLength(encoding); valueEndIndex = indexOfTerminator(data, valueStartIndex, encoding); } ImmutableList result = values.build(); return result.isEmpty() ? ImmutableList.of("""") : result; } @Nullable private static UrlLinkFrame decodeWxxxFrame(ParsableByteArray id3Data, int frameSize) { if (frameSize < 1) { // Frame is malformed. return null; } int encoding = id3Data.readUnsignedByte(); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, getCharset(encoding)); int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); int urlEndIndex = indexOfZeroByte(data, urlStartIndex); String url = decodeStringIfValid(data, urlStartIndex, urlEndIndex, Charsets.ISO_8859_1); return new UrlLinkFrame(""WXXX"", description, url); } private static UrlLinkFrame decodeUrlLinkFrame( ParsableByteArray id3Data, int frameSize, String id) { byte[] data = new byte[frameSize]; id3Data.readBytes(data, 0, frameSize); int urlEndIndex = indexOfZeroByte(data, 0); String url = new String(data, 0, urlEndIndex, Charsets.ISO_8859_1); return new UrlLinkFrame(id, null, url); } private static PrivFrame decodePrivFrame(ParsableByteArray id3Data, int frameSize) { byte[] data = new byte[frameSize]; id3Data.readBytes(data, 0, frameSize); int ownerEndIndex = indexOfZeroByte(data, 0); String owner = new String(data, 0, ownerEndIndex, Charsets.ISO_8859_1); int privateDataStartIndex = ownerEndIndex + 1; byte[] privateData = copyOfRangeIfValid(data, privateDataStartIndex, data.length); return new PrivFrame(owner, privateData); } private static GeobFrame decodeGeobFrame(ParsableByteArray id3Data, int frameSize) { int encoding = id3Data.readUnsignedByte(); Charset charset = getCharset(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); int mimeTypeEndIndex = indexOfZeroByte(data, 0); String mimeType = new String(data, 0, mimeTypeEndIndex, Charsets.ISO_8859_1); int filenameStartIndex = mimeTypeEndIndex + 1; int filenameEndIndex = indexOfTerminator(data, filenameStartIndex, encoding); String filename = decodeStringIfValid(data, filenameStartIndex, filenameEndIndex, charset); int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding); int descriptionEndIndex = indexOfTerminator(data, descriptionStartIndex, encoding); String description = decodeStringIfValid(data, descriptionStartIndex, descriptionEndIndex, charset); int objectDataStartIndex = descriptionEndIndex + delimiterLength(encoding); byte[] objectData = copyOfRangeIfValid(data, objectDataStartIndex, data.length); return new GeobFrame(mimeType, filename, description, objectData); } private static ApicFrame decodeApicFrame( ParsableByteArray id3Data, int frameSize, int majorVersion) { int encoding = id3Data.readUnsignedByte(); Charset charset = getCharset(encoding); byte[] data = new byte[frameSize - 1]; id3Data.readBytes(data, 0, frameSize - 1); String mimeType; int mimeTypeEndIndex; if (majorVersion == 2) { mimeTypeEndIndex = 2; mimeType = ""image/"" + Ascii.toLowerCase(new String(data, 0, 3, Charsets.ISO_8859_1)); if (""image/jpg"".equals(mimeType)) { mimeType = ""image/jpeg""; } } else { mimeTypeEndIndex = indexOfZeroByte(data, 0); mimeType = Ascii.toLowerCase(new String(data, 0, mimeTypeEndIndex, Charsets.ISO_8859_1)); if (mimeType.indexOf('/') == -1) { mimeType = ""image/"" + mimeType; } } int pictureType = data[mimeTypeEndIndex + 1] & 0xFF; int descriptionStartIndex = mimeTypeEndIndex + 2; int descriptionEndIndex = indexOfTerminator(data, descriptionStartIndex, encoding); String description = new String( data, descriptionStartIndex, descriptionEndIndex - descriptionStartIndex, charset); int pictureDataStartIndex = descriptionEndIndex + delimiterLength(encoding); byte[] pictureData = copyOfRangeIfValid(data, pictureDataStartIndex, data.length); return new ApicFrame(mimeType, description, pictureType, pictureData); } @Nullable private static CommentFrame decodeCommentFrame(ParsableByteArray id3Data, int frameSize) { if (frameSize < 4) { // Frame is malformed. return null; } int encoding = id3Data.readUnsignedByte(); Charset charset = getCharset(encoding); byte[] data = new byte[3]; id3Data.readBytes(data, 0, 3); String language = new String(data, 0, 3); data = new byte[frameSize - 4]; id3Data.readBytes(data, 0, frameSize - 4); int descriptionEndIndex = indexOfTerminator(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); int textStartIndex = descriptionEndIndex + delimiterLength(encoding); int textEndIndex = indexOfTerminator(data, textStartIndex, encoding); String text = decodeStringIfValid(data, textStartIndex, textEndIndex, charset); return new CommentFrame(language, description, text); } private static ChapterFrame decodeChapterFrame( ParsableByteArray id3Data, int frameSize, int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize, @Nullable FramePredicate framePredicate) { int framePosition = id3Data.getPosition(); int chapterIdEndIndex = indexOfZeroByte(id3Data.getData(), framePosition); String chapterId = new String( id3Data.getData(), framePosition, chapterIdEndIndex - framePosition, Charsets.ISO_8859_1); id3Data.setPosition(chapterIdEndIndex + 1); int startTime = id3Data.readInt(); int endTime = id3Data.readInt(); long startOffset = id3Data.readUnsignedInt(); if (startOffset == 0xFFFFFFFFL) { startOffset = C.INDEX_UNSET; } long endOffset = id3Data.readUnsignedInt(); if (endOffset == 0xFFFFFFFFL) { endOffset = C.INDEX_UNSET; } ArrayList subFrames = new ArrayList<>(); int limit = framePosition + frameSize; while (id3Data.getPosition() < limit) { Id3Frame frame = decodeFrame( majorVersion, id3Data, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); if (frame != null) { subFrames.add(frame); } } Id3Frame[] subFrameArray = subFrames.toArray(new Id3Frame[0]); return new ChapterFrame(chapterId, startTime, endTime, startOffset, endOffset, subFrameArray); } private static ChapterTocFrame decodeChapterTOCFrame( ParsableByteArray id3Data, int frameSize, int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize, @Nullable FramePredicate framePredicate) { int framePosition = id3Data.getPosition(); int elementIdEndIndex = indexOfZeroByte(id3Data.getData(), framePosition); String elementId = new String( id3Data.getData(), framePosition, elementIdEndIndex - framePosition, Charsets.ISO_8859_1); id3Data.setPosition(elementIdEndIndex + 1); int ctocFlags = id3Data.readUnsignedByte(); boolean isRoot = (ctocFlags & 0x0002) != 0; boolean isOrdered = (ctocFlags & 0x0001) != 0; int childCount = id3Data.readUnsignedByte(); String[] children = new String[childCount]; for (int i = 0; i < childCount; i++) { int startIndex = id3Data.getPosition(); int endIndex = indexOfZeroByte(id3Data.getData(), startIndex); children[i] = new String(id3Data.getData(), startIndex, endIndex - startIndex, Charsets.ISO_8859_1); id3Data.setPosition(endIndex + 1); } ArrayList subFrames = new ArrayList<>(); int limit = framePosition + frameSize; while (id3Data.getPosition() < limit) { @Nullable Id3Frame frame = decodeFrame( majorVersion, id3Data, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); if (frame != null) { subFrames.add(frame); } } Id3Frame[] subFrameArray = subFrames.toArray(new Id3Frame[0]); return new ChapterTocFrame(elementId, isRoot, isOrdered, children, subFrameArray); } private static MlltFrame decodeMlltFrame(ParsableByteArray id3Data, int frameSize) { // See ID3v2.4.0 native frames subsection 4.6. int mpegFramesBetweenReference = id3Data.readUnsignedShort(); int bytesBetweenReference = id3Data.readUnsignedInt24(); int millisecondsBetweenReference = id3Data.readUnsignedInt24(); int bitsForBytesDeviation = id3Data.readUnsignedByte(); int bitsForMillisecondsDeviation = id3Data.readUnsignedByte(); ParsableBitArray references = new ParsableBitArray(); references.reset(id3Data); int referencesBits = 8 * (frameSize - 10); int bitsPerReference = bitsForBytesDeviation + bitsForMillisecondsDeviation; int referencesCount = referencesBits / bitsPerReference; int[] bytesDeviations = new int[referencesCount]; int[] [MASK] s = new int[referencesCount]; for (int i = 0; i < referencesCount; i++) { int bytesDeviation = references.readBits(bitsForBytesDeviation); int [MASK] = references.readBits(bitsForMillisecondsDeviation); bytesDeviations[i] = bytesDeviation; [MASK] s[i] = [MASK] ; } return new MlltFrame( mpegFramesBetweenReference, bytesBetweenReference, millisecondsBetweenReference, bytesDeviations, [MASK] s); } private static BinaryFrame decodeBinaryFrame( ParsableByteArray id3Data, int frameSize, String id) { byte[] frame = new byte[frameSize]; id3Data.readBytes(frame, 0, frameSize); return new BinaryFrame(id, frame); } /** * Performs in-place removal of unsynchronization for {@code length} bytes starting from {@link * ParsableByteArray#getPosition()} * * @param data Contains the data to be processed. * @param length The length of the data to be processed. * @return The length of the data after processing. */ private static int removeUnsynchronization(ParsableByteArray data, int length) { byte[] bytes = data.getData(); int startPosition = data.getPosition(); for (int i = startPosition; i + 1 < startPosition + length; i++) { if ((bytes[i] & 0xFF) == 0xFF && bytes[i + 1] == 0x00) { int relativePosition = i - startPosition; System.arraycopy(bytes, i + 2, bytes, i + 1, length - relativePosition - 2); length--; } } return length; } /** Maps encoding byte from ID3v2 frame to a {@link Charset}. */ private static Charset getCharset(int encodingByte) { switch (encodingByte) { case ID3_TEXT_ENCODING_UTF_16: return Charsets.UTF_16; case ID3_TEXT_ENCODING_UTF_16BE: return Charsets.UTF_16BE; case ID3_TEXT_ENCODING_UTF_8: return Charsets.UTF_8; case ID3_TEXT_ENCODING_ISO_8859_1: default: return Charsets.ISO_8859_1; } } private static String getFrameId( int majorVersion, int frameId0, int frameId1, int frameId2, int frameId3) { return majorVersion == 2 ? String.format(Locale.US, ""%c%c%c"", frameId0, frameId1, frameId2) : String.format(Locale.US, ""%c%c%c%c"", frameId0, frameId1, frameId2, frameId3); } private static int indexOfTerminator(byte[] data, int fromIndex, int encoding) { int terminationPos = indexOfZeroByte(data, fromIndex); // For single byte encoding charsets, we're done. if (encoding == ID3_TEXT_ENCODING_ISO_8859_1 || encoding == ID3_TEXT_ENCODING_UTF_8) { return terminationPos; } // Otherwise ensure an even offset from the start, and look for a second zero byte. while (terminationPos < data.length - 1) { if ((terminationPos - fromIndex) % 2 == 0 && data[terminationPos + 1] == (byte) 0) { return terminationPos; } terminationPos = indexOfZeroByte(data, terminationPos + 1); } return data.length; } private static int indexOfZeroByte(byte[] data, int fromIndex) { for (int i = fromIndex; i < data.length; i++) { if (data[i] == (byte) 0) { return i; } } return data.length; } private static int delimiterLength(int encodingByte) { return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1 || encodingByte == ID3_TEXT_ENCODING_UTF_8) ? 1 : 2; } /** * Copies the specified range of an array, or returns a zero length array if the range is invalid. * * @param data The array from which to copy. * @param from The start of the range to copy (inclusive). * @param to The end of the range to copy (exclusive). * @return The copied data, or a zero length array if the range is invalid. */ private static byte[] copyOfRangeIfValid(byte[] data, int from, int to) { if (to <= from) { // Invalid or zero length range. return Util.EMPTY_BYTE_ARRAY; } return Arrays.copyOfRange(data, from, to); } /** * Returns a string obtained by decoding the specified range of {@code data} using the specified * {@code charset}. An empty string is returned if the range is invalid. * * @param data The array from which to decode the string. * @param from The start of the range. * @param to The end of the range (exclusive). * @param charset The {@link Charset} to use. * @return The decoded string, or an empty string if the range is invalid. */ private static String decodeStringIfValid(byte[] data, int from, int to, Charset charset) { if (to <= from || to > data.length) { return """"; } return new String(data, from, to - from, charset); } private static final class Id3Header { private final int majorVersion; private final boolean isUnsynchronized; private final int framesSize; public Id3Header(int majorVersion, boolean isUnsynchronized, int framesSize) { this.majorVersion = majorVersion; this.isUnsynchronized = isUnsynchronized; this.framesSize = framesSize; } } } ","millisecondsDeviation " "/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: DTMDocumentImpl.java 468653 2006-10-28 07:07:05Z minchau $ */ package org.apache.xml.dtm.ref; import javax.xml.transform.SourceLocator; import org.apache.xml.dtm.DTM; import org.apache.xml.dtm.DTMAxisIterator; import org.apache.xml.dtm.DTMAxisTraverser; import org.apache.xml.dtm.DTMManager; import org.apache.xml.dtm.DTMWSFilter; import org.apache.xml.utils.FastStringBuffer; import org.apache.xml.utils.XMLString; import org.apache.xml.utils.XMLStringFactory; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.ext.LexicalHandler; /** * This is the implementation of the DTM document interface. It receives * requests from an XML content handler similar to that of an XML DOM or SAX parser * to store information from the xml document in an array based * dtm table structure. This informtion is used later for document navigation, * query, and SAX event dispatch functions. The DTM can also be used directly as a * document composition model for an application. The requests received are: *
    *
  • initiating DTM to set the doc handle
  • *
  • resetting DTM for data structure reuse
  • *
  • hinting the end of document to adjust the end of data structure pointers
  • *
  • createnodes (element, comment, text, attribute, ....)
  • *
  • hinting the end of an element to patch parent and siblings
  • *
  • setting application provided symbol name stringpool data structures
  • *
*

State: In progress!!

* * %REVIEW% I _think_ the SAX convention is that ""no namespace"" is expressed * as """" rather than as null (which is the DOM's convention). What should * DTM expect? What should it do with the other? * *

Origin: the implemention is a composite logic based on the DTM of XalanJ1 and * DocImpl, DocumentImpl, ElementImpl, TextImpl, etc. of XalanJ2

*/ public class DTMDocumentImpl implements DTM, org.xml.sax.ContentHandler, org.xml.sax.ext.LexicalHandler { // Number of lower bits used to represent node index. protected static final byte DOCHANDLE_SHIFT = 22; // Masks the lower order of node handle. // Same as {@link DTMConstructor.IDENT_NODE_DEFAULT} protected static final int NODEHANDLE_MASK = (1 << (DOCHANDLE_SHIFT + 1)) - 1; // Masks the higher order Document handle // Same as {@link DTMConstructor.IDENT_DOC_DEFAULT} protected static final int DOCHANDLE_MASK = -1 - NODEHANDLE_MASK; int m_docHandle = NULL; // masked document handle for this dtm document int m_docElement = NULL; // nodeHandle to the root of the actual dtm doc content // Context for parse-and-append operations int currentParent = 0; // current parent - default is document root int previousSibling = 0; // previous sibling - no previous sibling protected int m_currentNode = -1; // current node // The tree under construction can itself be used as // the element stack, so m_elemStack isn't needed. //protected Stack m_elemStack = new Stack(); // element stack private boolean previousSiblingWasParent = false; // Local cache for record-at-a-time fetch int gotslot[] = new int[4]; // endDocument recieved? private boolean done = false; boolean m_isError = false; private final boolean DEBUG = false; /** The document base URI. */ protected String m_documentBaseURI; /** If we're building the model incrementally on demand, we need to * be able to tell the source when to send us more data. * * Note that if this has not been set, and you attempt to read ahead * of the current build point, we'll probably throw a null-pointer * exception. We could try to wait-and-retry instead, as a very poor * fallback, but that has all the known problems with multithreading * on multiprocessors and we Don't Want to Go There. * * @see setIncrementalSAXSource */ private IncrementalSAXSource m_incrSAXSource=null; // ========= DTM data structure declarations. ============== // nodes array: integer array blocks to hold the first level reference of the nodes, // each reference slot is addressed by a nodeHandle index value. // Assumes indices are not larger than {@link NODEHANDLE_MASK} // ({@link DOCHANDLE_SHIFT} bits). ChunkedIntArray nodes = new ChunkedIntArray(4); // text/comment table: string [MASK] to hold the text string values of the document, // each of which is addressed by the absolute offset and length in the [MASK] private FastStringBuffer m_char = new FastStringBuffer(); // Start of string currently being accumulated into m_char; // needed because the string may be appended in several chunks. private int m_char_current_start=0; // %TBD% INITIALIZATION/STARTUP ISSUES // -- Should we really be creating these, or should they be // passed in from outside? Scott want to be able to share // pools across multiple documents, so setting them here is // probably not the right default. private DTMStringPool m_localNames = new DTMStringPool(); private DTMStringPool m_nsNames = new DTMStringPool(); private DTMStringPool m_prefixNames = new DTMStringPool(); // %TBD% If we use the current ExpandedNameTable mapper, it // needs to be bound to the NS and local name pools. Which // means it needs to attach to them AFTER we've resolved their // startup. Or it needs to attach to this document and // retrieve them each time. Or this needs to be // an interface _implemented_ by this class... which might be simplest! private ExpandedNameTable m_expandedNames= new ExpandedNameTable(); private XMLStringFactory m_xsf; /** * Construct a DTM. * * @param documentNumber the ID number assigned to this document. * It will be shifted up into the high bits and returned as part of * all node ID numbers, so those IDs indicate which document they * came from as well as a location within the document. It is the * DTMManager's responsibility to assign a unique number to each * document. */ public DTMDocumentImpl(DTMManager mgr, int documentNumber, DTMWSFilter whiteSpaceFilter, XMLStringFactory xstringfactory){ initDocument(documentNumber); // clear nodes and document handle m_xsf = xstringfactory; } /** Bind a IncrementalSAXSource to this DTM. If we discover we need nodes * that have not yet been built, we will ask this object to send us more * events, and it will manage interactions with its data sources. * * Note that we do not actually build the IncrementalSAXSource, since we don't * know what source it's reading from, what thread that source will run in, * or when it will run. * * @param source The IncrementalSAXSource that we want to recieve events from * on demand. */ public void setIncrementalSAXSource(IncrementalSAXSource source) { m_incrSAXSource=source; // Establish SAX-stream link so we can receive the requested data source.setContentHandler(this); source.setLexicalHandler(this); // Are the following really needed? IncrementalSAXSource doesn't yet // support them, and they're mostly no-ops here... //source.setErrorHandler(this); //source.setDTDHandler(this); //source.setDeclHandler(this); } /** * Wrapper for ChunkedIntArray.append, to automatically update the * previous sibling's ""next"" reference (if necessary) and periodically * wake a reader who may have encountered incomplete data and entered * a wait state. * @param w0 int As in ChunkedIntArray.append * @param w1 int As in ChunkedIntArray.append * @param w2 int As in ChunkedIntArray.append * @param w3 int As in ChunkedIntArray.append * @return int As in ChunkedIntArray.append * @see ChunkedIntArray.append */ private final int appendNode(int w0, int w1, int w2, int w3) { // A decent compiler may inline this. int slotnumber = nodes.appendSlot(w0, w1, w2, w3); if (DEBUG) System.out.println(slotnumber+"": ""+w0+"" ""+w1+"" ""+w2+"" ""+w3); if (previousSiblingWasParent) nodes.writeEntry(previousSibling,2,slotnumber); previousSiblingWasParent = false; // Set the default; endElement overrides return slotnumber; } // ========= DTM Implementation Control Functions. ============== /** * Set an implementation dependent feature. *

* %REVIEW% Do we really expect to set features on DTMs? * * @param featureId A feature URL. * @param state true if this feature should be on, false otherwise. */ public void setFeature(String featureId, boolean state) {}; /** * Set a reference pointer to the element name symbol table. * %REVIEW% Should this really be Public? Changing it while * DTM is in use would be a disaster. * * @param poolRef DTMStringPool reference to an instance of table. */ public void setLocalNameTable(DTMStringPool poolRef) { m_localNames = poolRef; } /** * Get a reference pointer to the element name symbol table. * * @return DTMStringPool reference to an instance of table. */ public DTMStringPool getLocalNameTable() { return m_localNames; } /** * Set a reference pointer to the namespace URI symbol table. * %REVIEW% Should this really be Public? Changing it while * DTM is in use would be a disaster. * * @param poolRef DTMStringPool reference to an instance of table. */ public void setNsNameTable(DTMStringPool poolRef) { m_nsNames = poolRef; } /** * Get a reference pointer to the namespace URI symbol table. * * @return DTMStringPool reference to an instance of table. */ public DTMStringPool getNsNameTable() { return m_nsNames; } /** * Set a reference pointer to the prefix name symbol table. * %REVIEW% Should this really be Public? Changing it while * DTM is in use would be a disaster. * * @param poolRef DTMStringPool reference to an instance of table. */ public void setPrefixNameTable(DTMStringPool poolRef) { m_prefixNames = poolRef; } /** * Get a reference pointer to the prefix name symbol table. * * @return DTMStringPool reference to an instance of table. */ public DTMStringPool getPrefixNameTable() { return m_prefixNames; } /** * Set a reference pointer to the content-text repository * * @param [MASK] FastStringBuffer reference to an instance of * [MASK] */ void setContentBuffer(FastStringBuffer [MASK] ) { m_char = [MASK] ; } /** * Get a reference pointer to the content-text repository * * @return FastStringBuffer reference to an instance of [MASK] */ FastStringBuffer getContentBuffer() { return m_char; } /** getContentHandler returns ""our SAX builder"" -- the thing that * someone else should send SAX events to in order to extend this * DTM model. * * @return null if this model doesn't respond to SAX events, * ""this"" if the DTM object has a built-in SAX ContentHandler, * the IncrementalSAXSource if we're bound to one and should receive * the SAX stream via it for incremental build purposes... * */ public org.xml.sax.ContentHandler getContentHandler() { if (m_incrSAXSource instanceof IncrementalSAXSource_Filter) return (ContentHandler) m_incrSAXSource; else return this; } /** * Return this DTM's lexical handler. * * %REVIEW% Should this return null if constrution already done/begun? * * @return null if this model doesn't respond to lexical SAX events, * ""this"" if the DTM object has a built-in SAX ContentHandler, * the IncrementalSAXSource if we're bound to one and should receive * the SAX stream via it for incremental build purposes... */ public LexicalHandler getLexicalHandler() { if (m_incrSAXSource instanceof IncrementalSAXSource_Filter) return (LexicalHandler) m_incrSAXSource; else return this; } /** * Return this DTM's EntityResolver. * * @return null if this model doesn't respond to SAX entity ref events. */ public org.xml.sax.EntityResolver getEntityResolver() { return null; } /** * Return this DTM's DTDHandler. * * @return null if this model doesn't respond to SAX dtd events. */ public org.xml.sax.DTDHandler getDTDHandler() { return null; } /** * Return this DTM's ErrorHandler. * * @return null if this model doesn't respond to SAX error events. */ public org.xml.sax.ErrorHandler getErrorHandler() { return null; } /** * Return this DTM's DeclHandler. * * @return null if this model doesn't respond to SAX Decl events. */ public org.xml.sax.ext.DeclHandler getDeclHandler() { return null; } /** @return true iff we're building this model incrementally (eg * we're partnered with a IncrementalSAXSource) and thus require that the * transformation and the parse run simultaneously. Guidance to the * DTMManager. * */ public boolean needsTwoThreads() { return null!=m_incrSAXSource; } //================================================================ // ========= SAX2 ContentHandler methods ========= // Accept SAX events, use them to build/extend the DTM tree. // Replaces the deprecated DocumentHandler interface. public void characters(char[] ch, int start, int length) throws org.xml.sax.SAXException { // Actually creating the text node is handled by // processAccumulatedText(); here we just accumulate the // characters into the [MASK] . m_char.append(ch,start,length); } // Flush string accumulation into a text node private void processAccumulatedText() { int len=m_char.length(); if(len!=m_char_current_start) { // The FastStringBuffer has been previously agreed upon appendTextChild(m_char_current_start,len-m_char_current_start); m_char_current_start=len; } } public void endDocument() throws org.xml.sax.SAXException { // May need to tell the low-level builder code to pop up a level. // There _should't_ be any significant pending text at this point. appendEndDocument(); } public void endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName) throws org.xml.sax.SAXException { processAccumulatedText(); // No args but we do need to tell the low-level builder code to // pop up a level. appendEndElement(); } public void endPrefixMapping(java.lang.String prefix) throws org.xml.sax.SAXException { // No-op } public void ignorableWhitespace(char[] ch, int start, int length) throws org.xml.sax.SAXException { // %TBD% I believe ignorable text isn't part of the DTM model...? } public void processingInstruction(java.lang.String target, java.lang.String data) throws org.xml.sax.SAXException { processAccumulatedText(); // %TBD% Which pools do target and data go into? } public void setDocumentLocator(Locator locator) { // No-op for DTM } public void skippedEntity(java.lang.String name) throws org.xml.sax.SAXException { processAccumulatedText(); //%TBD% } public void startDocument() throws org.xml.sax.SAXException { appendStartDocument(); } public void startElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName, Attributes atts) throws org.xml.sax.SAXException { processAccumulatedText(); // %TBD% Split prefix off qname String prefix=null; int colon=qName.indexOf(':'); if(colon>0) prefix=qName.substring(0,colon); // %TBD% Where do we pool expandedName, or is it just the union, or... /**/System.out.println(""Prefix=""+prefix+"" index=""+m_prefixNames.stringToIndex(prefix)); appendStartElement(m_nsNames.stringToIndex(namespaceURI), m_localNames.stringToIndex(localName), m_prefixNames.stringToIndex(prefix)); /////// %TBD% // %TBD% I'm assuming that DTM will require resequencing of // NS decls before other attrs, hence two passes are taken. // %TBD% Is there an easier way to test for NSDecl? int nAtts=(atts==null) ? 0 : atts.getLength(); // %TBD% Countdown is more efficient if nobody cares about sequence. for(int i=nAtts-1;i>=0;--i) { qName=atts.getQName(i); if(qName.startsWith(""xmlns:"") || ""xmlns"".equals(qName)) { prefix=null; colon=qName.indexOf(':'); if(colon>0) { prefix=qName.substring(0,colon); } else { // %REVEIW% Null or """"? prefix=null; // Default prefix } appendNSDeclaration( m_prefixNames.stringToIndex(prefix), m_nsNames.stringToIndex(atts.getValue(i)), atts.getType(i).equalsIgnoreCase(""ID"")); } } for(int i=nAtts-1;i>=0;--i) { qName=atts.getQName(i); if(!(qName.startsWith(""xmlns:"") || ""xmlns"".equals(qName))) { // %TBD% I hate having to extract the prefix into a new // string when we may never use it. Consider pooling whole // qNames, which are already strings? prefix=null; colon=qName.indexOf(':'); if(colon>0) { prefix=qName.substring(0,colon); localName=qName.substring(colon+1); } else { prefix=""""; // Default prefix localName=qName; } m_char.append(atts.getValue(i)); // Single-string value int contentEnd=m_char.length(); if(!(""xmlns"".equals(prefix) || ""xmlns"".equals(qName))) appendAttribute(m_nsNames.stringToIndex(atts.getURI(i)), m_localNames.stringToIndex(localName), m_prefixNames.stringToIndex(prefix), atts.getType(i).equalsIgnoreCase(""ID""), m_char_current_start, contentEnd-m_char_current_start); m_char_current_start=contentEnd; } } } public void startPrefixMapping(java.lang.String prefix, java.lang.String uri) throws org.xml.sax.SAXException { // No-op in DTM, handled during element/attr processing? } // // LexicalHandler support. Not all SAX2 parsers support these events // but we may want to pass them through when they exist... // public void comment(char[] ch, int start, int length) throws org.xml.sax.SAXException { processAccumulatedText(); m_char.append(ch,start,length); // Single-string value appendComment(m_char_current_start,length); m_char_current_start+=length; } public void endCDATA() throws org.xml.sax.SAXException { // No-op in DTM } public void endDTD() throws org.xml.sax.SAXException { // No-op in DTM } public void endEntity(java.lang.String name) throws org.xml.sax.SAXException { // No-op in DTM } public void startCDATA() throws org.xml.sax.SAXException { // No-op in DTM } public void startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId) throws org.xml.sax.SAXException { // No-op in DTM } public void startEntity(java.lang.String name) throws org.xml.sax.SAXException { // No-op in DTM } //================================================================ // ========= Document Handler Functions ========= // %REVIEW% jjk -- DocumentHandler is SAX Level 1, and deprecated.... // and this wasn't a fully compliant or declared implementation of that API // in any case. Phase out in favor of SAX2 ContentHandler/LexicalHandler /** * Reset a dtm document to its initial (empty) state. * * The DTMManager will invoke this method when the dtm is created. * * @param documentNumber the handle for the DTM document. */ final void initDocument(int documentNumber) { // save masked DTM document handle m_docHandle = documentNumber<The content handler will invoke this method only once, and it will // * be the last method invoked during the parse. The handler shall not // * not invoke this method until it has either abandoned parsing // * (because of an unrecoverable error) or reached the end of // * input.

// */ // public void documentEnd() // { // done = true; // // %TBD% may need to notice the last slot number and slot count to avoid // // residual data from provious use of this DTM // } // /** // * Receive notification of the beginning of a document. // * // *

The SAX parser will invoke this method only once, before any // * other methods in this interface.

// */ // public void reset() // { // // %TBD% reset slot 0 to indicate ChunkedIntArray reuse or wait for // // the next initDocument(). // m_docElement = NULL; // reset nodeHandle to the root of the actual dtm doc content // initDocument(0); // } // /** // * Factory method; creates an Element node in this document. // * // * The node created will be chained according to its natural order of request // * received. %TBD% It can be rechained later via the optional DTM writable interface. // * // *

The XML content handler will invoke endElement() method after all // * of the element's content are processed in order to give DTM the indication // * to prepare and patch up parent and sibling node pointers.

// * // *

The following interface for createElement will use an index value corresponds // * to the symbol entry in the DTMDStringPool based symbol tables.

// * // * @param nsIndex The namespace of the node // * @param nameIndex The element name. // * @see #endElement // * @see org.xml.sax.Attributes // * @return nodeHandle int of the element created // */ // public int createElement(int nsIndex, int nameIndex, Attributes atts) // { // // do document root node creation here on the first element, create nodes for // // this element and its attributes, store the element, namespace, and attritute // // name indexes to the nodes array, keep track of the current node and parent // // element used // // W0 High: Namespace Low: Node Type // int w0 = (nsIndex << 16) | ELEMENT_NODE; // // W1: Parent // int w1 = currentParent; // // W2: Next (initialized as 0) // int w2 = 0; // // W3: Tagname // int w3 = nameIndex; // //int ourslot = nodes.appendSlot(w0, w1, w2, w3); // int ourslot = appendNode(w0, w1, w2, w3); // currentParent = ourslot; // previousSibling = 0; // setAttributes(atts); // // set the root element pointer when creating the first element node // if (m_docElement == NULL) // m_docElement = ourslot; // return (m_docHandle | ourslot); // } // // Factory method to create an Element node not associated with a given name space // // using String value parameters passed in from a content handler or application // /** // * Factory method; creates an Element node not associated with a given name space in this document. // * // * The node created will be chained according to its natural order of request // * received. %TBD% It can be rechained later via the optional DTM writable interface. // * // *

The XML content handler or application will invoke endElement() method after all // * of the element's content are processed in order to give DTM the indication // * to prepare and patch up parent and sibling node pointers.

// * // *

The following parameters for createElement contains raw string values for name // * symbols used in an Element node.

// * // * @param name String the element name, including the prefix if any. // * @param atts The attributes attached to the element, if any. // * @see #endElement // * @see org.xml.sax.Attributes // */ // public int createElement(String name, Attributes atts) // { // // This method wraps around the index valued interface of the createElement interface. // // The raw string values are stored into the current DTM name symbol tables. The method // // method will then use the index values returned to invoke the other createElement() // // onverted to index values modified to match a // // method. // int nsIndex = NULL; // int nameIndex = m_localNames.stringToIndex(name); // // note - there should be no prefix separator in the name because it is not associated // // with a name space // return createElement(nsIndex, nameIndex, atts); // } // // Factory method to create an Element node associated with a given name space // // using String value parameters passed in from a content handler or application // /** // * Factory method; creates an Element node associated with a given name space in this document. // * // * The node created will be chained according to its natural order of request // * received. %TBD% It can be rechained later via the optional DTM writable interface. // * // *

The XML content handler or application will invoke endElement() method after all // * of the element's content are processed in order to give DTM the indication // * to prepare and patch up parent and sibling node pointers.

// * // *

The following parameters for createElementNS contains raw string values for name // * symbols used in an Element node.

// * // * @param ns String the namespace of the node // * @param name String the element name, including the prefix if any. // * @param atts The attributes attached to the element, if any. // * @see #endElement // * @see org.xml.sax.Attributes // */ // public int createElementNS(String ns, String name, Attributes atts) // { // // This method wraps around the index valued interface of the createElement interface. // // The raw string values are stored into the current DTM name symbol tables. The method // // method will then use the index values returned to invoke the other createElement() // // onverted to index values modified to match a // // method. // int nsIndex = m_nsNames.stringToIndex(ns); // int nameIndex = m_localNames.stringToIndex(name); // // The prefixIndex is not needed by the indexed interface of the createElement method // int prefixSep = name.indexOf("":""); // int prefixIndex = m_prefixNames.stringToIndex(name.substring(0, prefixSep)); // return createElement(nsIndex, nameIndex, atts); // } // /** // * Receive an indication for the end of an element. // * // *

The XML content handler will invoke this method at the end of every // * element in the XML document to give hint its time to pop up the current // * element and parent and patch up parent and sibling pointers if necessary // * // *

%tbd% The following interface may need to be modified to match a // * coordinated access to the DTMDStringPool based symbol tables.

// * // * @param ns the namespace of the element // * @param name The element name // */ // public void endElement(String ns, String name) // { // // pop up the stacks // // // if (previousSiblingWasParent) // nodes.writeEntry(previousSibling, 2, NULL); // // Pop parentage // previousSibling = currentParent; // nodes.readSlot(currentParent, gotslot); // currentParent = gotslot[1] & 0xFFFF; // // The element just being finished will be // // the previous sibling for the next operation // previousSiblingWasParent = true; // // Pop a level of namespace table // // namespaceTable.removeLastElem(); // } // /** // * Creates attributes for the current node. // * // * @param atts Attributes to be created. // */ // void setAttributes(Attributes atts) { // int atLength = (null == atts) ? 0 : atts.getLength(); // for (int i=0; i < atLength; i++) { // String qname = atts.getQName(i); // createAttribute(atts.getQName(i), atts.getValue(i)); // } // } // /** // * Appends an attribute to the document. // * @param qname Qualified Name of the attribute // * @param value Value of the attribute // * @return Handle of node // */ // public int createAttribute(String qname, String value) { // int colonpos = qname.indexOf("":""); // String attName = qname.substring(colonpos+1); // int w0 = 0; // if (colonpos > 0) { // String prefix = qname.substring(0, colonpos); // if (prefix.equals(""xml"")) { // //w0 = ATTRIBUTE_NODE | // // (org.apache.xalan.templates.Constants.S_XMLNAMESPACEURI << 16); // } else { // //w0 = ATTRIBUTE_NODE | // } // } else { // w0 = ATTRIBUTE_NODE; // } // // W1: Parent // int w1 = currentParent; // // W2: Next (not yet resolved) // int w2 = 0; // // W3: Tag name // int w3 = m_localNames.stringToIndex(attName); // // Add node // int ourslot = appendNode(w0, w1, w2, w3); // previousSibling = ourslot; // Should attributes be previous siblings // // W0: Node Type // w0 = TEXT_NODE; // // W1: Parent // w1 = ourslot; // // W2: Start Position within [MASK] // w2 = m_char.length(); // m_char.append(value); // // W3: Length // w3 = m_char.length() - w2; // appendNode(w0, w1, w2, w3); // charStringStart=m_char.length(); // charStringLength = 0; // //previousSibling = ourslot; // // Attrs are Parents // previousSiblingWasParent = true; // return (m_docHandle | ourslot); // } // /** // * Factory method; creates a Text node in this document. // * // * The node created will be chained according to its natural order of request // * received. %TBD% It can be rechained later via the optional DTM writable interface. // * // * @param text String The characters text string from the XML document. // * @return int DTM node-number of the text node created // */ // public int createTextNode(String text) // throws DTMException // { // // wraps around the index value based createTextNode method // return createTextNode(text.toCharArray(), 0, text.length()); // } // /** // * Factory method; creates a Text node in this document. // * // * The node created will be chained according to its natural order of request // * received. %TBD% It can be rechained later via the optional DTM writable interface. // * // * %REVIEW% for text normalization issues, unless we are willing to // * insist that all adjacent text must be merged before this method // * is called. // * // * @param ch The characters from the XML document. // * @param start The start position in the array. // * @param length The number of characters to read from the array. // */ // public int createTextNode(char ch[], int start, int length) // throws DTMException // { // m_char.append(ch, start, length); // store the chunk to the text/comment string table // // create a Text Node // // %TBD% may be possible to combine with appendNode()to replace the next chunk of code // int w0 = TEXT_NODE; // // W1: Parent // int w1 = currentParent; // // W2: Start position within m_char // int w2 = charStringStart; // // W3: Length of the full string // int w3 = length; // int ourslot = appendNode(w0, w1, w2, w3); // previousSibling = ourslot; // charStringStart=m_char.length(); // charStringLength = 0; // return (m_docHandle | ourslot); // } // /** // * Factory method; creates a Comment node in this document. // * // * The node created will be chained according to its natural order of request // * received. %TBD% It can be rechained later via the optional DTM writable interface. // * // * @param text String The characters text string from the XML document. // * @return int DTM node-number of the text node created // */ // public int createComment(String text) // throws DTMException // { // // wraps around the index value based createTextNode method // return createComment(text.toCharArray(), 0, text.length()); // } // /** // * Factory method; creates a Comment node in this document. // * // * The node created will be chained according to its natural order of request // * received. %TBD% It can be rechained later via the optional DTM writable interface. // * // * @param ch An array holding the characters in the comment. // * @param start The starting position in the array. // * @param length The number of characters to use from the array. // * @see DTMException // */ // public int createComment(char ch[], int start, int length) // throws DTMException // { // m_char.append(ch, start, length); // store the comment string to the text/comment string table // // create a Comment Node // // %TBD% may be possible to combine with appendNode()to replace the next chunk of code // int w0 = COMMENT_NODE; // // W1: Parent // int w1 = currentParent; // // W2: Start position within m_char // int w2 = charStringStart; // // W3: Length of the full string // int w3 = length; // int ourslot = appendNode(w0, w1, w2, w3); // previousSibling = ourslot; // charStringStart=m_char.length(); // charStringLength = 0; // return (m_docHandle | ourslot); // } // // Counters to keep track of the current text string being accumulated with respect // // to the text/comment string table: charStringStart should point to the starting // // offset of the string in the table and charStringLength the acccumulated length when // // appendAccumulatedText starts, and reset to the end of the table and 0 at the end // // of appendAccumulatedText for the next set of characters receives // int charStringStart=0,charStringLength=0; // ========= Document Navigation Functions ========= /** Given a node handle, test if it has child nodes. *

%REVIEW% This is obviously useful at the DOM layer, where it * would permit testing this without having to create a proxy * node. It's less useful in the DTM API, where * (dtm.getFirstChild(nodeHandle)!=DTM.NULL) is just as fast and * almost as self-evident. But it's a convenience, and eases porting * of DOM code to DTM.

* * @param nodeHandle int Handle of the node. * @return int true if the given node has child nodes. */ public boolean hasChildNodes(int nodeHandle) { return(getFirstChild(nodeHandle) != NULL); } /** * Given a node handle, get the handle of the node's first child. * If not yet resolved, waits for more nodes to be added to the document and * tries again. * * @param nodeHandle int Handle of the node. * @return int DTM node-number of first child, or DTM.NULL to indicate none exists. */ public int getFirstChild(int nodeHandle) { // ###shs worry about tracing/debug later nodeHandle &= NODEHANDLE_MASK; // Read node into variable nodes.readSlot(nodeHandle, gotslot); // type is the last half of first slot short type = (short) (gotslot[0] & 0xFFFF); // Check to see if Element or Document node if ((type == ELEMENT_NODE) || (type == DOCUMENT_NODE) || (type == ENTITY_REFERENCE_NODE)) { // In case when Document root is given // if (nodeHandle == 0) nodeHandle = 1; // %TBD% Probably was a mistake. // If someone explicitly asks for first child // of Document, I would expect them to want // that and only that. int kid = nodeHandle + 1; nodes.readSlot(kid, gotslot); while (ATTRIBUTE_NODE == (gotslot[0] & 0xFFFF)) { // points to next sibling kid = gotslot[2]; // Return NULL if node has only attributes if (kid == NULL) return NULL; nodes.readSlot(kid, gotslot); } // If parent slot matches given parent, return kid if (gotslot[1] == nodeHandle) { int firstChild = kid | m_docHandle; return firstChild; } } // No child found return NULL; } /** * Given a node handle, advance to its last child. * If not yet resolved, waits for more nodes to be added to the document and * tries again. * * @param nodeHandle int Handle of the node. * @return int Node-number of last child, * or DTM.NULL to indicate none exists. */ public int getLastChild(int nodeHandle) { // ###shs put trace/debug later nodeHandle &= NODEHANDLE_MASK; // do not need to test node type since getFirstChild does that int lastChild = NULL; for (int nextkid = getFirstChild(nodeHandle); nextkid != NULL; nextkid = getNextSibling(nextkid)) { lastChild = nextkid; } return lastChild | m_docHandle; } /** * Retrieves an attribute node by by qualified name and namespace URI. * * @param nodeHandle int Handle of the node upon which to look up this attribute. * @param namespaceURI The namespace URI of the attribute to * retrieve, or null. * @param name The local name of the attribute to * retrieve. * @return The attribute node handle with the specified name ( * nodeName) or DTM.NULL if there is no such * attribute. */ public int getAttributeNode(int nodeHandle, String namespaceURI, String name) { int nsIndex = m_nsNames.stringToIndex(namespaceURI), nameIndex = m_localNames.stringToIndex(name); nodeHandle &= NODEHANDLE_MASK; nodes.readSlot(nodeHandle, gotslot); short type = (short) (gotslot[0] & 0xFFFF); // If nodeHandle points to element next slot would be first attribute if (type == ELEMENT_NODE) nodeHandle++; // Iterate through Attribute Nodes while (type == ATTRIBUTE_NODE) { if ((nsIndex == (gotslot[0] << 16)) && (gotslot[3] == nameIndex)) return nodeHandle | m_docHandle; // Goto next sibling nodeHandle = gotslot[2]; nodes.readSlot(nodeHandle, gotslot); } return NULL; } /** * Given a node handle, get the index of the node's first attribute. * * @param nodeHandle int Handle of the Element node. * @return Handle of first attribute, or DTM.NULL to indicate none exists. */ public int getFirstAttribute(int nodeHandle) { nodeHandle &= NODEHANDLE_MASK; // %REVIEW% jjk: Just a quick observation: If you're going to // call readEntry repeatedly on the same node, it may be // more efficiently to do a readSlot to get the data locally, // reducing the addressing and call-and-return overhead. // Should we check if handle is element (do we want sanity checks?) if (ELEMENT_NODE != (nodes.readEntry(nodeHandle, 0) & 0xFFFF)) return NULL; // First Attribute (if any) should be at next position in table nodeHandle++; return(ATTRIBUTE_NODE == (nodes.readEntry(nodeHandle, 0) & 0xFFFF)) ? nodeHandle | m_docHandle : NULL; } /** * Given a node handle, get the index of the node's first child. * If not yet resolved, waits for more nodes to be added to the document and * tries again * * @param nodeHandle handle to node, which should probably be an element * node, but need not be. * * @param inScope true if all namespaces in scope should be returned, * false if only the namespace declarations should be * returned. * @return handle of first namespace, or DTM.NULL to indicate none exists. */ public int getFirstNamespaceNode(int nodeHandle, boolean inScope) { return NULL; } /** * Given a node handle, advance to its next sibling. * * %TBD% This currently uses the DTM-internal definition of * sibling; eg, the last attr's next sib is the first * child. In the old DTM, the DOM proxy layer provided the * additional logic for the public view. If we're rewriting * for XPath emulation, that test must be done here. * * %TBD% CODE INTERACTION WITH INCREMENTAL PARSE - If not yet * resolved, should wait for more nodes to be added to the document * and tries again. * * @param nodeHandle int Handle of the node. * @return int Node-number of next sibling, * or DTM.NULL to indicate none exists. * */ public int getNextSibling(int nodeHandle) { nodeHandle &= NODEHANDLE_MASK; // Document root has no next sibling if (nodeHandle == 0) return NULL; short type = (short) (nodes.readEntry(nodeHandle, 0) & 0xFFFF); if ((type == ELEMENT_NODE) || (type == ATTRIBUTE_NODE) || (type == ENTITY_REFERENCE_NODE)) { int nextSib = nodes.readEntry(nodeHandle, 2); if (nextSib == NULL) return NULL; if (nextSib != 0) return (m_docHandle | nextSib); // ###shs should cycle/wait if nextSib is 0? Working on threading next } // Next Sibling is in the next position if it shares the same parent int thisParent = nodes.readEntry(nodeHandle, 1); if (nodes.readEntry(++nodeHandle, 1) == thisParent) return (m_docHandle | nodeHandle); return NULL; } /** * Given a node handle, find its preceeding sibling. * WARNING: DTM is asymmetric; this operation is resolved by search, and is * relatively expensive. * * @param nodeHandle the id of the node. * @return int Node-number of the previous sib, * or DTM.NULL to indicate none exists. */ public int getPreviousSibling(int nodeHandle) { nodeHandle &= NODEHANDLE_MASK; // Document root has no previous sibling if (nodeHandle == 0) return NULL; int parent = nodes.readEntry(nodeHandle, 1); int kid = NULL; for (int nextkid = getFirstChild(parent); nextkid != nodeHandle; nextkid = getNextSibling(nextkid)) { kid = nextkid; } return kid | m_docHandle; } /** * Given a node handle, advance to the next attribute. If an * element, we advance to its first attribute; if an attr, we advance to * the next attr on the same node. * * @param nodeHandle int Handle of the node. * @return int DTM node-number of the resolved attr, * or DTM.NULL to indicate none exists. */ public int getNextAttribute(int nodeHandle) { nodeHandle &= NODEHANDLE_MASK; nodes.readSlot(nodeHandle, gotslot); //%REVIEW% Why are we using short here? There's no storage //reduction for an automatic variable, especially one used //so briefly, and it typically costs more cycles to process //than an int would. short type = (short) (gotslot[0] & 0xFFFF); if (type == ELEMENT_NODE) { return getFirstAttribute(nodeHandle); } else if (type == ATTRIBUTE_NODE) { if (gotslot[2] != NULL) return (m_docHandle | gotslot[2]); } return NULL; } /** * Given a namespace handle, advance to the next namespace. * * %TBD% THIS METHOD DOES NOT MATCH THE CURRENT SIGNATURE IN * THE DTM INTERFACE. FIX IT, OR JUSTIFY CHANGING THE DTM * API. * * @param namespaceHandle handle to node which must be of type NAMESPACE_NODE. * @return handle of next namespace, or DTM.NULL to indicate none exists. */ public int getNextNamespaceNode(int baseHandle,int namespaceHandle, boolean inScope) { // ###shs need to work on namespace return NULL; } /** * Given a node handle, advance to its next descendant. * If not yet resolved, waits for more nodes to be added to the document and * tries again. * * @param subtreeRootHandle * @param nodeHandle int Handle of the node. * @return handle of next descendant, * or DTM.NULL to indicate none exists. */ public int getNextDescendant(int subtreeRootHandle, int nodeHandle) { subtreeRootHandle &= NODEHANDLE_MASK; nodeHandle &= NODEHANDLE_MASK; // Document root [Document Node? -- jjk] - no next-sib if (nodeHandle == 0) return NULL; while (!m_isError) { // Document done and node out of bounds if (done && (nodeHandle > nodes.slotsUsed())) break; if (nodeHandle > subtreeRootHandle) { nodes.readSlot(nodeHandle+1, gotslot); if (gotslot[2] != 0) { short type = (short) (gotslot[0] & 0xFFFF); if (type == ATTRIBUTE_NODE) { nodeHandle +=2; } else { int nextParentPos = gotslot[1]; if (nextParentPos >= subtreeRootHandle) return (m_docHandle | (nodeHandle+1)); else break; } } else if (!done) { // Add wait logic here } else break; } else { nodeHandle++; } } // Probably should throw error here like original instead of returning return NULL; } /** * Given a node handle, advance to the next node on the following axis. * * @param axisContextHandle the start of the axis that is being traversed. * @param nodeHandle * @return handle of next sibling, * or DTM.NULL to indicate none exists. */ public int getNextFollowing(int axisContextHandle, int nodeHandle) { //###shs still working on return NULL; } /** * Given a node handle, advance to the next node on the preceding axis. * * @param axisContextHandle the start of the axis that is being traversed. * @param nodeHandle the id of the node. * @return int Node-number of preceding sibling, * or DTM.NULL to indicate none exists. */ public int getNextPreceding(int axisContextHandle, int nodeHandle) { // ###shs copied from Xalan 1, what is this suppose to do? nodeHandle &= NODEHANDLE_MASK; while (nodeHandle > 1) { nodeHandle--; if (ATTRIBUTE_NODE == (nodes.readEntry(nodeHandle, 0) & 0xFFFF)) continue; // if nodeHandle is _not_ an ancestor of // axisContextHandle, specialFind will return it. // If it _is_ an ancestor, specialFind will return -1 // %REVIEW% unconditional return defeats the // purpose of the while loop -- does this // logic make any sense? return (m_docHandle | nodes.specialFind(axisContextHandle, nodeHandle)); } return NULL; } /** * Given a node handle, find its parent node. * * @param nodeHandle the id of the node. * @return int Node-number of parent, * or DTM.NULL to indicate none exists. */ public int getParent(int nodeHandle) { // Should check to see within range? // Document Root should not have to be handled differently return (m_docHandle | nodes.readEntry(nodeHandle, 1)); } /** * Returns the root element of the document. * @return nodeHandle to the Document Root. */ public int getDocumentRoot() { return (m_docHandle | m_docElement); } /** * Given a node handle, find the owning document node. * * @return int Node handle of document, which should always be valid. */ public int getDocument() { return m_docHandle; } /** * Given a node handle, find the owning document node. This has the exact * same semantics as the DOM Document method of the same name, in that if * the nodeHandle is a document node, it will return NULL. * *

%REVIEW% Since this is DOM-specific, it may belong at the DOM * binding layer. Included here as a convenience function and to * aid porting of DOM code to DTM.

* * @param nodeHandle the id of the node. * @return int Node handle of owning document, or NULL if the nodeHandle is * a document. */ public int getOwnerDocument(int nodeHandle) { // Assumption that Document Node is always in 0 slot if ((nodeHandle & NODEHANDLE_MASK) == 0) return NULL; return (nodeHandle & DOCHANDLE_MASK); } /** * Given a node handle, find the owning document node. This has the DTM * semantics; a Document node is its own owner. * *

%REVIEW% Since this is DOM-specific, it may belong at the DOM * binding layer. Included here as a convenience function and to * aid porting of DOM code to DTM.

* * @param nodeHandle the id of the node. * @return int Node handle of owning document, or NULL if the nodeHandle is * a document. */ public int getDocumentRoot(int nodeHandle) { // Assumption that Document Node is always in 0 slot if ((nodeHandle & NODEHANDLE_MASK) == 0) return NULL; return (nodeHandle & DOCHANDLE_MASK); } /** * Get the string-value of a node as a String object * (see http://www.w3.org/TR/xpath#data-model * for the definition of a node's string-value). * * @param nodeHandle The node ID. * * @return A string object that represents the string-value of the given node. */ public XMLString getStringValue(int nodeHandle) { // ###zaj - researching nodes.readSlot(nodeHandle, gotslot); int nodetype=gotslot[0] & 0xFF; String value=null; switch (nodetype) { case TEXT_NODE: case COMMENT_NODE: case CDATA_SECTION_NODE: value= m_char.getString(gotslot[2], gotslot[3]); break; case PROCESSING_INSTRUCTION_NODE: case ATTRIBUTE_NODE: case ELEMENT_NODE: case ENTITY_REFERENCE_NODE: default: break; } return m_xsf.newstr( value ); } /** * Get number of character array chunks in * the string-value of a node. * (see http://www.w3.org/TR/xpath#data-model * for the definition of a node's string-value). * Note that a single text node may have multiple text chunks. * * EXPLANATION: This method is an artifact of the fact that the * underlying m_chars object may not store characters in a * single contiguous array -- for example,the current * FastStringBuffer may split a single node's text across * multiple allocation units. This call tells us how many * separate accesses will be required to retrieve the entire * content. PLEASE NOTE that this may not be the same as the * number of SAX characters() events that caused the text node * to be built in the first place, since m_chars [MASK] ing may * be on different boundaries than the parser's [MASK] s. * * @param nodeHandle The node ID. * * @return number of character array chunks in * the string-value of a node. * */ //###zaj - tbd public int getStringValueChunkCount(int nodeHandle) { //###zaj return value return 0; } /** * Get a character array chunk in the string-value of a node. * (see http://www.w3.org/TR/xpath#data-model * for the definition of a node's string-value). * Note that a single text node may have multiple text chunks. * * EXPLANATION: This method is an artifact of the fact that * the underlying m_chars object may not store characters in a * single contiguous array -- for example,the current * FastStringBuffer may split a single node's text across * multiple allocation units. This call retrieves a single * contiguous portion of the text -- as much as m-chars was * able to store in a single allocation unit. PLEASE NOTE * that this may not be the same granularityas the SAX * characters() events that caused the text node to be built * in the first place, since m_chars [MASK] ing may be on * different boundaries than the parser's [MASK] s. * * @param nodeHandle The node ID. * @param chunkIndex Which chunk to get. * @param startAndLen An array of 2 where the start position and length of * the chunk will be returned. * * @return The character array reference where the chunk occurs. */ //###zaj - tbd public char[] getStringValueChunk(int nodeHandle, int chunkIndex, int[] startAndLen) {return new char[0];} /** * Given a node handle, return an ID that represents the node's expanded name. * * @param nodeHandle The handle to the node in question. * * @return the expanded-name id of the node. */ public int getExpandedTypeID(int nodeHandle) { nodes.readSlot(nodeHandle, gotslot); String qName = m_localNames.indexToString(gotslot[3]); // Remove prefix from qName // %TBD% jjk This is assuming the elementName is the qName. int colonpos = qName.indexOf("":""); String localName = qName.substring(colonpos+1); // Get NS String namespace = m_nsNames.indexToString(gotslot[0] << 16); // Create expanded name String expandedName = namespace + "":"" + localName; int expandedNameID = m_nsNames.stringToIndex(expandedName); return expandedNameID; } /** * Given an expanded name, return an ID. If the expanded-name does not * exist in the internal tables, the entry will be created, and the ID will * be returned. Any additional nodes that are created that have this * expanded name will use this ID. * * @return the expanded-name id of the node. */ public int getExpandedTypeID(String namespace, String localName, int type) { // Create expanded name // %TBD% jjk Expanded name is bitfield-encoded as // typeID[6]nsuriID[10]localID[16]. Switch to that form, and to // accessing the ns/local via their tables rather than confusing // nsnames and expandednames. String expandedName = namespace + "":"" + localName; int expandedNameID = m_nsNames.stringToIndex(expandedName); return expandedNameID; } /** * Given an expanded-name ID, return the local name part. * * @param ExpandedNameID an ID that represents an expanded-name. * @return String Local name of this node. */ public String getLocalNameFromExpandedNameID(int ExpandedNameID) { // Get expanded name String expandedName = m_localNames.indexToString(ExpandedNameID); // Remove prefix from expanded name int colonpos = expandedName.indexOf("":""); String localName = expandedName.substring(colonpos+1); return localName; } /** * Given an expanded-name ID, return the namespace URI part. * * @param ExpandedNameID an ID that represents an expanded-name. * @return String URI value of this node's namespace, or null if no * namespace was resolved. */ public String getNamespaceFromExpandedNameID(int ExpandedNameID) { String expandedName = m_localNames.indexToString(ExpandedNameID); // Remove local name from expanded name int colonpos = expandedName.indexOf("":""); String nsName = expandedName.substring(0, colonpos); return nsName; } /** * fixednames */ private static final String[] fixednames= { null,null, // nothing, Element null,""#text"", // Attr, Text ""#cdata_section"",null, // CDATA, EntityReference null,null, // Entity, PI ""#comment"",""#document"", // Comment, Document null,""#document-fragment"", // Doctype, DocumentFragment null}; // Notation /** * Given a node handle, return its DOM-style node name. This will * include names such as #text or #document. * * @param nodeHandle the id of the node. * @return String Name of this node, which may be an empty string. * %REVIEW% Document when empty string is possible... */ public String getNodeName(int nodeHandle) { nodes.readSlot(nodeHandle, gotslot); short type = (short) (gotslot[0] & 0xFFFF); String name = fixednames[type]; if (null == name) { int i=gotslot[3]; /**/System.out.println(""got i=""+i+"" ""+(i>>16)+""/""+(i&0xffff)); name=m_localNames.indexToString(i & 0xFFFF); String prefix=m_prefixNames.indexToString(i >>16); if(prefix!=null && prefix.length()>0) name=prefix+"":""+name; } return name; } /** * Given a node handle, return the XPath node name. This should be * the name as described by the XPath data model, NOT the DOM-style * name. * * @param nodeHandle the id of the node. * @return String Name of this node. */ public String getNodeNameX(int nodeHandle) {return null;} /** * Given a node handle, return its DOM-style localname. * (As defined in Namespaces, this is the portion of the name after any * colon character) * * %REVIEW% What's the local name of something other than Element/Attr? * Should this be DOM-style (undefined unless namespaced), or other? * * @param nodeHandle the id of the node. * @return String Local name of this node. */ public String getLocalName(int nodeHandle) { nodes.readSlot(nodeHandle, gotslot); short type = (short) (gotslot[0] & 0xFFFF); String name = """"; if ((type==ELEMENT_NODE) || (type==ATTRIBUTE_NODE)) { int i=gotslot[3]; name=m_localNames.indexToString(i & 0xFFFF); if(name==null) name=""""; } return name; } /** * Given a namespace handle, return the prefix that the namespace decl is * mapping. * Given a node handle, return the prefix used to map to the namespace. * *

%REVIEW% Are you sure you want """" for no prefix?

* * %REVIEW% Should this be DOM-style (undefined unless namespaced), * or other? * * @param nodeHandle the id of the node. * @return String prefix of this node's name, or """" if no explicit * namespace prefix was given. */ public String getPrefix(int nodeHandle) { nodes.readSlot(nodeHandle, gotslot); short type = (short) (gotslot[0] & 0xFFFF); String name = """"; if((type==ELEMENT_NODE) || (type==ATTRIBUTE_NODE)) { int i=gotslot[3]; name=m_prefixNames.indexToString(i >>16); if(name==null) name=""""; } return name; } /** * Given a node handle, return its DOM-style namespace URI * (As defined in Namespaces, this is the declared URI which this node's * prefix -- or default in lieu thereof -- was mapped to.) * * @param nodeHandle the id of the node. * @return String URI value of this node's namespace, or null if no * namespace was resolved. */ public String getNamespaceURI(int nodeHandle) {return null;} /** * Given a node handle, return its node value. This is mostly * as defined by the DOM, but may ignore some conveniences. *

* * @param nodeHandle The node id. * @return String Value of this node, or null if not * meaningful for this node type. */ public String getNodeValue(int nodeHandle) { nodes.readSlot(nodeHandle, gotslot); int nodetype=gotslot[0] & 0xFF; // ###zaj use mask to get node type String value=null; switch (nodetype) { // ###zaj todo - document nodetypes case ATTRIBUTE_NODE: nodes.readSlot(nodeHandle+1, gotslot); case TEXT_NODE: case COMMENT_NODE: case CDATA_SECTION_NODE: value=m_char.getString(gotslot[2], gotslot[3]); //###zaj break; case PROCESSING_INSTRUCTION_NODE: case ELEMENT_NODE: case ENTITY_REFERENCE_NODE: default: break; } return value; } /** * Given a node handle, return its DOM-style node type. *

* %REVIEW% Generally, returning short is false economy. Return int? * * @param nodeHandle The node id. * @return int Node type, as per the DOM's Node._NODE constants. */ public short getNodeType(int nodeHandle) { return(short) (nodes.readEntry(nodeHandle, 0) & 0xFFFF); } /** * Get the depth level of this node in the tree (equals 1 for * a parentless node). * * @param nodeHandle The node id. * @return the number of ancestors, plus one * @xsl.usage internal */ public short getLevel(int nodeHandle) { short count = 0; while (nodeHandle != 0) { count++; nodeHandle = nodes.readEntry(nodeHandle, 1); } return count; } // ============== Document query functions ============== /** * Tests whether DTM DOM implementation implements a specific feature and * that feature is supported by this node. * * @param feature The name of the feature to test. * @param version This is the version number of the feature to test. * If the version is not * specified, supporting any version of the feature will cause the * method to return true. * @return Returns true if the specified feature is * supported on this node, false otherwise. */ public boolean isSupported(String feature, String version) {return false;} /** * Return the base URI of the document entity. If it is not known * (because the document was parsed from a socket connection or from * standard input, for example), the value of this property is unknown. * * @return the document base URI String object or null if unknown. */ public String getDocumentBaseURI() { return m_documentBaseURI; } /** * Set the base URI of the document entity. * * @param baseURI the document base URI String object or null if unknown. */ public void setDocumentBaseURI(String baseURI) { m_documentBaseURI = baseURI; } /** * Return the system identifier of the document entity. If * it is not known, the value of this property is unknown. * * @param nodeHandle The node id, which can be any valid node handle. * @return the system identifier String object or null if unknown. */ public String getDocumentSystemIdentifier(int nodeHandle) {return null;} /** * Return the name of the character encoding scheme * in which the document entity is expressed. * * @param nodeHandle The node id, which can be any valid node handle. * @return the document encoding String object. */ public String getDocumentEncoding(int nodeHandle) {return null;} /** * Return an indication of the standalone status of the document, * either ""yes"" or ""no"". This property is derived from the optional * standalone document declaration in the XML declaration at the * beginning of the document entity, and has no value if there is no * standalone document declaration. * * @param nodeHandle The node id, which can be any valid node handle. * @return the document standalone String object, either ""yes"", ""no"", or null. */ public String getDocumentStandalone(int nodeHandle) {return null;} /** * Return a string representing the XML version of the document. This * property is derived from the XML declaration optionally present at the * beginning of the document entity, and has no value if there is no XML * declaration. * * @param documentHandle the document handle * * @return the document version String object */ public String getDocumentVersion(int documentHandle) {return null;} /** * Return an indication of * whether the processor has read the complete DTD. Its value is a * boolean. If it is false, then certain properties (indicated in their * descriptions below) may be unknown. If it is true, those properties * are never unknown. * * @return true if all declarations were processed {}; * false otherwise. */ public boolean getDocumentAllDeclarationsProcessed() {return false;} /** * A document type declaration information item has the following properties: * * 1. [system identifier] The system identifier of the external subset, if * it exists. Otherwise this property has no value. * * @return the system identifier String object, or null if there is none. */ public String getDocumentTypeDeclarationSystemIdentifier() {return null;} /** * Return the public identifier of the external subset, * normalized as described in 4.2.2 External Entities [XML]. If there is * no external subset or if it has no public identifier, this property * has no value. * * @return the public identifier String object, or null if there is none. */ public String getDocumentTypeDeclarationPublicIdentifier() {return null;} /** * Returns the Element whose ID is given by * elementId. If no such element exists, returns * DTM.NULL. Behavior is not defined if more than one element * has this ID. Attributes (including those * with the name ""ID"") are not of type ID unless so defined by DTD/Schema * information available to the DTM implementation. * Implementations that do not know whether attributes are of type ID or * not are expected to return DTM.NULL. * *

%REVIEW% Presumably IDs are still scoped to a single document, * and this operation searches only within a single document, right? * Wouldn't want collisions between DTMs in the same process.

* * @param elementId The unique id value for an element. * @return The handle of the matching element. */ public int getElementById(String elementId) {return 0;} /** * The getUnparsedEntityURI function returns the URI of the unparsed * entity with the specified name in the same document as the context * node (see [3.3 Unparsed Entities]). It returns the empty string if * there is no such entity. *

* XML processors may choose to use the System Identifier (if one * is provided) to resolve the entity, rather than the URI in the * Public Identifier. The details are dependent on the processor, and * we would have to support some form of plug-in resolver to handle * this properly. Currently, we simply return the System Identifier if * present, and hope that it a usable URI or that our caller can * map it to one. * TODO: Resolve Public Identifiers... or consider changing function name. *

* If we find a relative URI * reference, XML expects it to be resolved in terms of the base URI * of the document. The DOM doesn't do that for us, and it isn't * entirely clear whether that should be done here; currently that's * pushed up to a higher level of our application. (Note that DOM Level * 1 didn't store the document's base URI.) * TODO: Consider resolving Relative URIs. *

* (The DOM's statement that ""An XML processor may choose to * completely expand entities before the structure model is passed * to the DOM"" refers only to parsed entities, not unparsed, and hence * doesn't affect this function.) * * @param name A string containing the Entity Name of the unparsed * entity. * * @return String containing the URI of the Unparsed Entity, or an * empty string if no such entity exists. */ public String getUnparsedEntityURI(String name) {return null;} // ============== Boolean methods ================ /** * Return true if the xsl:strip-space or xsl:preserve-space was processed * during construction of the DTM document. * *

%REVEIW% Presumes a 1:1 mapping from DTM to Document, since * we aren't saying which Document to query...?

*/ public boolean supportsPreStripping() {return false;} /** * Figure out whether nodeHandle2 should be considered as being later * in the document than nodeHandle1, in Document Order as defined * by the XPath model. This may not agree with the ordering defined * by other XML applications. *

* There are some cases where ordering isn't defined, and neither are * the results of this function -- though we'll generally return true. * * TODO: Make sure this does the right thing with attribute nodes!!! * * @param nodeHandle1 DOM Node to perform position comparison on. * @param nodeHandle2 DOM Node to perform position comparison on . * * @return false if node2 comes before node1, otherwise return true. * You can think of this as * (node1.documentOrderPosition <= node2.documentOrderPosition). */ public boolean isNodeAfter(int nodeHandle1, int nodeHandle2) {return false;} /** * 2. [element content whitespace] A boolean indicating whether the * character is white space appearing within element content (see [XML], * 2.10 ""White Space Handling""). Note that validating XML processors are * required by XML 1.0 to provide this information. If there is no * declaration for the containing element, this property has no value for * white space characters. If no declaration has been read, but the [all * declarations processed] property of the document information item is * false (so there may be an unread declaration), then the value of this * property is unknown for white space characters. It is always false for * characters that are not white space. * * @param nodeHandle the node ID. * @return true if the character data is whitespace; * false otherwise. */ public boolean isCharacterElementContentWhitespace(int nodeHandle) {return false;} /** * 10. [all declarations processed] This property is not strictly speaking * part of the infoset of the document. Rather it is an indication of * whether the processor has read the complete DTD. Its value is a * boolean. If it is false, then certain properties (indicated in their * descriptions below) may be unknown. If it is true, those properties * are never unknown. * * @param documentHandle A node handle that must identify a document. * @return true if all declarations were processed; * false otherwise. */ public boolean isDocumentAllDeclarationsProcessed(int documentHandle) {return false;} /** * 5. [specified] A flag indicating whether this attribute was actually * specified in the start-tag of its element, or was defaulted from the * DTD. * * @param attributeHandle the attribute handle * @return true if the attribute was specified; * false if it was defaulted. */ public boolean isAttributeSpecified(int attributeHandle) {return false;} // ========== Direct SAX Dispatch, for optimization purposes ======== /** * Directly call the * characters method on the passed ContentHandler for the * string-value of the given node (see http://www.w3.org/TR/xpath#data-model * for the definition of a node's string-value). Multiple calls to the * ContentHandler's characters methods may well occur for a single call to * this method. * * @param nodeHandle The node ID. * @param ch A non-null reference to a ContentHandler. * * @throws org.xml.sax.SAXException */ public void dispatchCharactersEvents( int nodeHandle, org.xml.sax.ContentHandler ch, boolean normalize) throws org.xml.sax.SAXException {} /** * Directly create SAX parser events from a subtree. * * @param nodeHandle The node ID. * @param ch A non-null reference to a ContentHandler. * * @throws org.xml.sax.SAXException */ public void dispatchToEvents(int nodeHandle, org.xml.sax.ContentHandler ch) throws org.xml.sax.SAXException {} /** * Return an DOM node for the given node. * * @param nodeHandle The node ID. * * @return A node representation of the DTM node. */ public org.w3c.dom.Node getNode(int nodeHandle) { return null; } // ==== Construction methods (may not be supported by some implementations!) ===== // %REVIEW% jjk: These probably aren't the right API. At the very least // they need to deal with current-insertion-location and end-element // issues. /** * Append a child to the end of the child list of the current node. Please note that the node * is always cloned if it is owned by another document. * *

%REVIEW% ""End of the document"" needs to be defined more clearly. * Does it become the last child of the Document? Of the root element?

* * @param newChild Must be a valid new node handle. * @param clone true if the child should be cloned into the document. * @param cloneDepth if the clone argument is true, specifies that the * clone should include all it's children. */ public void appendChild(int newChild, boolean clone, boolean cloneDepth) { boolean sameDoc = ((newChild & DOCHANDLE_MASK) == m_docHandle); if (clone || !sameDoc) { } else { } } /** * Append a text node child that will be constructed from a string, * to the end of the document. * *

%REVIEW% ""End of the document"" needs to be defined more clearly. * Does it become the last child of the Document? Of the root element?

* * @param str Non-null reference to a string. */ public void appendTextChild(String str) { // ###shs Think more about how this differs from createTextNode //%TBD% } //================================================================ // ==== BUILDER methods ==== // %TBD% jjk: SHOULD PROBABLY BE INLINED, unless we want to support // both SAX1 and SAX2 and share this logic between them. /** Append a text child at the current insertion point. Assumes that the * actual content of the text has previously been appended to the m_char * [MASK] (shared with the builder). * * @param m_char_current_start int Starting offset of node's content in m_char. * @param contentLength int Length of node's content in m_char. * */ void appendTextChild(int m_char_current_start,int contentLength) { // create a Text Node // %TBD% may be possible to combine with appendNode()to replace the next chunk of code int w0 = TEXT_NODE; // W1: Parent int w1 = currentParent; // W2: Start position within m_char int w2 = m_char_current_start; // W3: Length of the full string int w3 = contentLength; int ourslot = appendNode(w0, w1, w2, w3); previousSibling = ourslot; } /** Append a comment child at the current insertion point. Assumes that the * actual content of the comment has previously been appended to the m_char * [MASK] (shared with the builder). * * @param m_char_current_start int Starting offset of node's content in m_char. * @param contentLength int Length of node's content in m_char. * */ void appendComment(int m_char_current_start,int contentLength) { // create a Comment Node // %TBD% may be possible to combine with appendNode()to replace the next chunk of code int w0 = COMMENT_NODE; // W1: Parent int w1 = currentParent; // W2: Start position within m_char int w2 = m_char_current_start; // W3: Length of the full string int w3 = contentLength; int ourslot = appendNode(w0, w1, w2, w3); previousSibling = ourslot; } /** Append an Element child at the current insertion point. This * Element then _becomes_ the insertion point; subsequent appends * become its lastChild until an appendEndElement() call is made. * * Assumes that the symbols (local name, namespace URI and prefix) * have already been added to the pools * * Note that this _only_ handles the Element node itself. Attrs and * namespace nodes are unbundled in the ContentHandler layer * and appended separately. * * @param namespaceIndex: Index within the namespaceURI string pool * @param localNameIndex Index within the local name string pool * @param prefixIndex: Index within the prefix string pool * */ void appendStartElement(int namespaceIndex,int localNameIndex, int prefixIndex) { // do document root node creation here on the first element, create nodes for // this element and its attributes, store the element, namespace, and attritute // name indexes to the nodes array, keep track of the current node and parent // element used // W0 High: Namespace Low: Node Type int w0 = (namespaceIndex << 16) | ELEMENT_NODE; // W1: Parent int w1 = currentParent; // W2: Next (initialized as 0) int w2 = 0; // W3: Tagname high: prefix Low: local name int w3 = localNameIndex | prefixIndex<<16; /**/System.out.println(""set w3=""+w3+"" ""+(w3>>16)+""/""+(w3&0xffff)); //int ourslot = nodes.appendSlot(w0, w1, w2, w3); int ourslot = appendNode(w0, w1, w2, w3); currentParent = ourslot; previousSibling = 0; // set the root element pointer when creating the first element node if (m_docElement == NULL) m_docElement = ourslot; } /** Append a Namespace Declaration child at the current insertion point. * Assumes that the symbols (namespace URI and prefix) have already been * added to the pools * * @param prefixIndex: Index within the prefix string pool * @param namespaceIndex: Index within the namespaceURI string pool * @param isID: If someone really insists on writing a bad DTD, it is * theoretically possible for a namespace declaration to also be declared * as being a node ID. I don't really want to support that stupidity, * but I'm not sure we can refuse to accept it. * */ void appendNSDeclaration(int prefixIndex, int namespaceIndex, boolean isID) { // %REVIEW% I'm assigning this node the ""namespace for namespaces"" // which the DOM defined. It is expected that the Namespace spec will // adopt this as official. It isn't strictly needed since it's implied // by the nodetype, but for now... // %REVIEW% Prefix need not be recorded; it's implied too. But // recording it might simplify the design. // %TBD% isID is not currently honored. final int namespaceForNamespaces=m_nsNames.stringToIndex(""http://www.w3.org/2000/xmlns/""); // W0 High: Namespace Low: Node Type int w0 = NAMESPACE_NODE | (m_nsNames.stringToIndex(""http://www.w3.org/2000/xmlns/"")<<16); // W1: Parent int w1 = currentParent; // W2: CURRENTLY UNUSED -- It's next-sib in attrs, but we have no kids. int w2 = 0; // W3: namespace name int w3 = namespaceIndex; // Add node int ourslot = appendNode(w0, w1, w2, w3); previousSibling = ourslot; // Should attributes be previous siblings previousSiblingWasParent = false; return ;//(m_docHandle | ourslot); } /** Append an Attribute child at the current insertion * point. Assumes that the symbols (namespace URI, local name, and * prefix) have already been added to the pools, and that the content has * already been appended to m_char. Note that the attribute's content has * been flattened into a single string; DTM does _NOT_ attempt to model * the details of entity references within attribute values. * * @param namespaceIndex int Index within the namespaceURI string pool * @param localNameIndex int Index within the local name string pool * @param prefixIndex int Index within the prefix string pool * @param isID boolean True if this attribute was declared as an ID * (for use in supporting getElementByID). * @param m_char_current_start int Starting offset of node's content in m_char. * @param contentLength int Length of node's content in m_char. * */ void appendAttribute(int namespaceIndex, int localNameIndex, int prefixIndex, boolean isID, int m_char_current_start, int contentLength) { // %TBD% isID is not currently honored. // W0 High: Namespace Low: Node Type int w0 = ATTRIBUTE_NODE | namespaceIndex<<16; // W1: Parent int w1 = currentParent; // W2: Next (not yet resolved) int w2 = 0; // W3: Tagname high: prefix Low: local name int w3 = localNameIndex | prefixIndex<<16; /**/System.out.println(""set w3=""+w3+"" ""+(w3>>16)+""/""+(w3&0xffff)); // Add node int ourslot = appendNode(w0, w1, w2, w3); previousSibling = ourslot; // Should attributes be previous siblings // Attribute's content is currently appended as a Text Node // W0: Node Type w0 = TEXT_NODE; // W1: Parent w1 = ourslot; // W2: Start Position within [MASK] w2 = m_char_current_start; // W3: Length w3 = contentLength; appendNode(w0, w1, w2, w3); // Attrs are Parents previousSiblingWasParent = true; return ;//(m_docHandle | ourslot); } /** * This returns a stateless ""traverser"", that can navigate over an * XPath axis, though not in document order. * * @param axis One of Axes.ANCESTORORSELF, etc. * * @return A DTMAxisIterator, or null if the given axis isn't supported. */ public DTMAxisTraverser getAxisTraverser(final int axis) { return null; } /** * This is a shortcut to the iterators that implement the * supported XPath axes (only namespace::) is not supported. * Returns a bare-bones iterator that must be initialized * with a start node (using iterator.setStartNode()). * * @param axis One of Axes.ANCESTORORSELF, etc. * * @return A DTMAxisIterator, or null if the given axis isn't supported. */ public DTMAxisIterator getAxisIterator(final int axis) { // %TBD% return null; } /** * Get an iterator that can navigate over an XPath Axis, predicated by * the extended type ID. * * * @param axis * @param type An extended type ID. * * @return A DTMAxisIterator, or null if the given axis isn't supported. */ public DTMAxisIterator getTypedAxisIterator(final int axis, final int type) { // %TBD% return null; } /** Terminate the element currently acting as an insertion point. Subsequent * insertions will occur as the last child of this element's parent. * */ void appendEndElement() { // pop up the stacks if (previousSiblingWasParent) nodes.writeEntry(previousSibling, 2, NULL); // Pop parentage previousSibling = currentParent; nodes.readSlot(currentParent, gotslot); currentParent = gotslot[1] & 0xFFFF; // The element just being finished will be // the previous sibling for the next operation previousSiblingWasParent = true; // Pop a level of namespace table // namespaceTable.removeLastElem(); } /** Starting a new document. Perform any resets/initialization * not already handled. * */ void appendStartDocument() { // %TBD% reset slot 0 to indicate ChunkedIntArray reuse or wait for // the next initDocument(). m_docElement = NULL; // reset nodeHandle to the root of the actual dtm doc content initDocument(0); } /** All appends to this document have finished; do whatever final * cleanup is needed. * */ void appendEndDocument() { done = true; // %TBD% may need to notice the last slot number and slot count to avoid // residual data from provious use of this DTM } /** * For the moment all the run time properties are ignored by this * class. * * @param property a String value * @param value an Object value */ public void setProperty(String property, Object value) { } /** * Source information is not handled yet, so return * null here. * * @param node an int value * @return null */ public SourceLocator getSourceLocatorFor(int node) { return null; } /** * A dummy routine to satisify the abstract interface. If the DTM * implememtation that extends the default base requires notification * of registration, they can override this method. */ public void documentRegistration() { } /** * A dummy routine to satisify the abstract interface. If the DTM * implememtation that extends the default base requires notification * when the document is being released, they can override this method */ public void documentRelease() { } /** * Migrate a DTM built with an old DTMManager to a new DTMManager. * After the migration, the new DTMManager will treat the DTM as * one that is built by itself. * This is used to support DTM sharing between multiple transformations. * @param manager the DTMManager */ public void migrateTo(DTMManager manager) { } } ","buffer " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.registry.nacos; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.selector.AbstractSelector; import java.util.List; public class MockNamingService implements NamingService { @Override public void registerInstance(String serviceName, String ip, int port) { } @Override public void registerInstance(String serviceName, String groupName, String ip, int port) { } @Override public void registerInstance(String serviceName, String ip, int port, String clusterName) { } @Override public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName) { } @Override public void registerInstance(String serviceName, Instance instance) { } @Override public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { } @Override public void batchRegisterInstance(String serviceName, String groupName, List instances) { } @Override public void deregisterInstance(String serviceName, String ip, int port) { } @Override public void deregisterInstance(String serviceName, String groupName, String ip, int port) { } @Override public void deregisterInstance(String serviceName, String ip, int port, String clusterName) { } @Override public void deregisterInstance(String serviceName, String groupName, String ip, int port, String clusterName) { } @Override public void deregisterInstance(String serviceName, Instance instance) { } @Override public void deregisterInstance(String serviceName, String groupName, Instance instance) { } @Override public void batchDeregisterInstance(String s, String s1, List list) throws NacosException { } @Override public List getAllInstances(String serviceName) { return null; } @Override public List getAllInstances(String serviceName, String groupName) throws NacosException { return null; } @Override public List getAllInstances(String serviceName, boolean subscribe) throws NacosException { return null; } @Override public List getAllInstances(String serviceName, String groupName, boolean subscribe) { return null; } @Override public List getAllInstances(String serviceName, List [MASK] ) { return null; } @Override public List getAllInstances(String serviceName, String groupName, List [MASK] ) { return null; } @Override public List getAllInstances(String serviceName, List [MASK] , boolean subscribe) { return null; } @Override public List getAllInstances(String serviceName, String groupName, List [MASK] , boolean subscribe) { return null; } @Override public List selectInstances(String serviceName, boolean healthy) { return null; } @Override public List selectInstances(String serviceName, String groupName, boolean healthy) { return null; } @Override public List selectInstances(String serviceName, boolean healthy, boolean subscribe) { return null; } @Override public List selectInstances(String serviceName, String groupName, boolean healthy, boolean subscribe) { return null; } @Override public List selectInstances(String serviceName, List [MASK] , boolean healthy) { return null; } @Override public List selectInstances(String serviceName, String groupName, List [MASK] , boolean healthy) { return null; } @Override public List selectInstances(String serviceName, List [MASK] , boolean healthy, boolean subscribe) { return null; } @Override public List selectInstances(String serviceName, String groupName, List [MASK] , boolean healthy, boolean subscribe) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, boolean subscribe) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName, boolean subscribe) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, List [MASK] ) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName, List [MASK] ) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, List [MASK] , boolean subscribe) { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName, List [MASK] , boolean subscribe) { return null; } @Override public void subscribe(String serviceName, EventListener listener) throws NacosException { } @Override public void subscribe(String serviceName, String groupName, EventListener listener) throws NacosException { } @Override public void subscribe(String serviceName, List [MASK] , EventListener listener) throws NacosException { } @Override public void subscribe(String serviceName, String groupName, List [MASK] , EventListener listener) throws NacosException { } @Override public void unsubscribe(String serviceName, EventListener listener) { } @Override public void unsubscribe(String serviceName, String groupName, EventListener listener) { } @Override public void unsubscribe(String serviceName, List [MASK] , EventListener listener) { } @Override public void unsubscribe(String serviceName, String groupName, List [MASK] , EventListener listener) { } @Override public ListView getServicesOfServer(int pageNo, int pageSize) { return null; } @Override public ListView getServicesOfServer(int pageNo, int pageSize, String groupName) { return null; } @Override public ListView getServicesOfServer(int pageNo, int pageSize, AbstractSelector selector) { return null; } @Override public ListView getServicesOfServer(int pageNo, int pageSize, String groupName, AbstractSelector selector) { return null; } @Override public List getSubscribeServices() { return null; } @Override public String getServerStatus() { return null; } @Override public void shutDown() { } } ","clusters " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 2001-2016, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ /** * Port From: ICU4C v1.8.1 : format : NumberFormatTest * Source File: $ICU4oot/source/test/intltest/numfmtst.cpp **/ package android.icu.dev.test.format; import java.io.IOException; import java.math.BigInteger; import java.text.AttributedCharacterIterator; import java.text.FieldPosition; import java.text.Format; import java.text.ParseException; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; import org.junit.Test; import android.icu.dev.test.TestFmwk; import android.icu.dev.test.TestUtil; import android.icu.dev.test.format.IntlTestDecimalFormatAPIC.FieldContainer; import android.icu.impl.ICUConfig; import android.icu.impl.LocaleUtility; import android.icu.impl.data.ResourceReader; import android.icu.impl.data.TokenIterator; import android.icu.math.BigDecimal; import android.icu.math.MathContext; import android.icu.text.CompactDecimalFormat; import android.icu.text.DecimalFormat; import android.icu.text.DecimalFormatSymbols; import android.icu.text.DisplayContext; import android.icu.text.MeasureFormat; import android.icu.text.NumberFormat; import android.icu.text.NumberFormat.NumberFormatFactory; import android.icu.text.NumberFormat.SimpleNumberFormatFactory; import android.icu.text.NumberingSystem; import android.icu.text.RuleBasedNumberFormat; import android.icu.util.Currency; import android.icu.util.CurrencyAmount; import android.icu.util.ULocale; public class NumberFormatTest extends TestFmwk { private static ULocale EN = new ULocale(""en""); private static Number toNumber(String s) { if (s.equals(""NaN"")) { return Double.NaN; } else if (s.equals(""-Inf"")) { return Double.NEGATIVE_INFINITY; } else if (s.equals(""Inf"")) { return Double.POSITIVE_INFINITY; } return new BigDecimal(s); } private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { @Override public Character Id() { return 'J'; } @Override public String format(NumberFormatTestData tuple) { DecimalFormat fmt = newDecimalFormat(tuple); String actual = fmt.format(toNumber(tuple.format)); String expected = tuple.output; if (!expected.equals(actual)) { return ""Expected "" + expected + "", got "" + actual; } return null; } @Override public String toPattern(NumberFormatTestData tuple) { DecimalFormat fmt = newDecimalFormat(tuple); StringBuilder result = new StringBuilder(); if (tuple.toPattern != null) { String expected = tuple.toPattern; String actual = fmt.toPattern(); if (!expected.equals(actual)) { result.append(""Expected toPattern="" + expected + "", got "" + actual); } } if (tuple.toLocalizedPattern != null) { String expected = tuple.toLocalizedPattern; String actual = fmt.toLocalizedPattern(); if (!expected.equals(actual)) { result.append(""Expected toLocalizedPattern="" + expected + "", got "" + actual); } } return result.length() == 0 ? null : result.toString(); } @Override public String parse(NumberFormatTestData tuple) { DecimalFormat fmt = newDecimalFormat(tuple); ParsePosition ppos = new ParsePosition(0); Number actual = fmt.parse(tuple.parse, ppos); if (ppos.getIndex() == 0) { if (!tuple.output.equals(""fail"")) { return ""Parse error expected.""; } return null; } if (tuple.output.equals(""fail"")) { return ""Parse succeeded: ""+actual+"", but was expected to fail.""; } Number expected = toNumber(tuple.output); // number types cannot be compared, this is the best we can do. if (expected.doubleValue() != (actual.doubleValue())) { return ""Expected: "" + expected + "", got: "" + actual; } return null; } @Override public String parseCurrency(NumberFormatTestData tuple) { DecimalFormat fmt = newDecimalFormat(tuple); ParsePosition ppos = new ParsePosition(0); CurrencyAmount currAmt = fmt.parseCurrency(tuple.parse, ppos); if (ppos.getIndex() == 0) { if (!tuple.output.equals(""fail"")) { return ""Parse error expected.""; } return null; } if (tuple.output.equals(""fail"")) { return ""Parse succeeded: ""+currAmt+"", but was expected to fail.""; } Number expected = toNumber(tuple.output); Number actual = currAmt.getNumber(); // number types cannot be compared, this is the best we can do. if (expected.doubleValue() != (actual.doubleValue())) { return ""Expected: "" + expected + "", got: "" + actual; } if (!tuple.outputCurrency.equals(currAmt.getCurrency().toString())) { return ""Expected currency: "" + tuple.outputCurrency + "", got: "" + currAmt.getCurrency(); } return null; } /** * @param tuple * @return */ private DecimalFormat newDecimalFormat(NumberFormatTestData tuple) { DecimalFormat fmt = new DecimalFormat( tuple.pattern == null ? ""0"" : tuple.pattern, new DecimalFormatSymbols(tuple.locale == null ? EN : tuple.locale)); adjustDecimalFormat(tuple, fmt); return fmt; } /** * @param tuple * @param fmt */ private void adjustDecimalFormat(NumberFormatTestData tuple, DecimalFormat fmt) { if (tuple.minIntegerDigits != null) { fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); } if (tuple.maxIntegerDigits != null) { fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); } if (tuple.minFractionDigits != null) { fmt.setMinimumFractionDigits(tuple.minFractionDigits); } if (tuple.maxFractionDigits != null) { fmt.setMaximumFractionDigits(tuple.maxFractionDigits); } if (tuple.currency != null) { fmt.setCurrency(tuple.currency); } if (tuple.minGroupingDigits != null) { // Oops we don't support this. } if (tuple.useSigDigits != null) { fmt.setSignificantDigitsUsed( tuple.useSigDigits != 0); } if (tuple.minSigDigits != null) { fmt.setMinimumSignificantDigits(tuple.minSigDigits); } if (tuple.maxSigDigits != null) { fmt.setMaximumSignificantDigits(tuple.maxSigDigits); } if (tuple.useGrouping != null) { fmt.setGroupingUsed(tuple.useGrouping != 0); } if (tuple.multiplier != null) { fmt.setMultiplier(tuple.multiplier); } if (tuple.roundingIncrement != null) { fmt.setRoundingIncrement(tuple.roundingIncrement.doubleValue()); } if (tuple.formatWidth != null) { fmt.setFormatWidth(tuple.formatWidth); } if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { fmt.setPadCharacter(tuple.padCharacter.charAt(0)); } if (tuple.useScientific != null) { fmt.setScientificNotation(tuple.useScientific != 0); } if (tuple.grouping != null) { fmt.setGroupingSize(tuple.grouping); } if (tuple.grouping2 != null) { fmt.setSecondaryGroupingSize(tuple.grouping2); } if (tuple.roundingMode != null) { fmt.setRoundingMode(tuple.roundingMode); } if (tuple.currencyUsage != null) { fmt.setCurrencyUsage(tuple.currencyUsage); } if (tuple.minimumExponentDigits != null) { fmt.setMinimumExponentDigits( tuple.minimumExponentDigits.byteValue()); } if (tuple.exponentSignAlwaysShown != null) { fmt.setExponentSignAlwaysShown( tuple.exponentSignAlwaysShown != 0); } if (tuple.decimalSeparatorAlwaysShown != null) { fmt.setDecimalSeparatorAlwaysShown( tuple.decimalSeparatorAlwaysShown != 0); } if (tuple.padPosition != null) { fmt.setPadPosition(tuple.padPosition); } if (tuple.positivePrefix != null) { fmt.setPositivePrefix(tuple.positivePrefix); } if (tuple.positiveSuffix != null) { fmt.setPositiveSuffix(tuple.positiveSuffix); } if (tuple.negativePrefix != null) { fmt.setNegativePrefix(tuple.negativePrefix); } if (tuple.negativeSuffix != null) { fmt.setNegativeSuffix(tuple.negativeSuffix); } if (tuple.localizedPattern != null) { fmt.applyLocalizedPattern(tuple.localizedPattern); } int lenient = tuple.lenient == null ? 1 : tuple.lenient.intValue(); fmt.setParseStrict(lenient == 0); if (tuple.parseIntegerOnly != null) { fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); } if (tuple.decimalPatternMatchRequired != null) { fmt.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0); } if (tuple.parseNoExponent != null) { // Oops, not supported for now } } }; private DataDrivenNumberFormatTestUtility.CodeUnderTest JDK = new DataDrivenNumberFormatTestUtility.CodeUnderTest() { @Override public Character Id() { return 'K'; } @Override public String format(NumberFormatTestData tuple) { java.text.DecimalFormat fmt = newDecimalFormat(tuple); String actual = fmt.format(toNumber(tuple.format)); String expected = tuple.output; if (!expected.equals(actual)) { return ""Expected "" + expected + "", got "" + actual; } return null; } @Override public String toPattern(NumberFormatTestData tuple) { java.text.DecimalFormat fmt = newDecimalFormat(tuple); StringBuilder result = new StringBuilder(); if (tuple.toPattern != null) { String expected = tuple.toPattern; String actual = fmt.toPattern(); if (!expected.equals(actual)) { result.append(""Expected toPattern="" + expected + "", got "" + actual); } } if (tuple.toLocalizedPattern != null) { String expected = tuple.toLocalizedPattern; String actual = fmt.toLocalizedPattern(); if (!expected.equals(actual)) { result.append(""Expected toLocalizedPattern="" + expected + "", got "" + actual); } } return result.length() == 0 ? null : result.toString(); } @Override public String parse(NumberFormatTestData tuple) { java.text.DecimalFormat fmt = newDecimalFormat(tuple); ParsePosition ppos = new ParsePosition(0); Number actual = fmt.parse(tuple.parse, ppos); if (ppos.getIndex() == 0) { if (!tuple.output.equals(""fail"")) { return ""Parse error expected.""; } return null; } if (tuple.output.equals(""fail"")) { return ""Parse succeeded: ""+actual+"", but was expected to fail.""; } Number expected = toNumber(tuple.output); // number types cannot be compared, this is the best we can do. if (expected.doubleValue() != actual.doubleValue()) { return ""Expected: "" + expected + "", got: "" + actual; } return null; } /** * @param tuple * @return */ private java.text.DecimalFormat newDecimalFormat(NumberFormatTestData tuple) { java.text.DecimalFormat fmt = new java.text.DecimalFormat( tuple.pattern == null ? ""0"" : tuple.pattern, new java.text.DecimalFormatSymbols( (tuple.locale == null ? EN : tuple.locale).toLocale())); adjustDecimalFormat(tuple, fmt); return fmt; } /** * @param tuple * @param fmt */ private void adjustDecimalFormat(NumberFormatTestData tuple, java.text.DecimalFormat fmt) { if (tuple.minIntegerDigits != null) { fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); } if (tuple.maxIntegerDigits != null) { fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); } if (tuple.minFractionDigits != null) { fmt.setMinimumFractionDigits(tuple.minFractionDigits); } if (tuple.maxFractionDigits != null) { fmt.setMaximumFractionDigits(tuple.maxFractionDigits); } if (tuple.currency != null) { fmt.setCurrency(java.util.Currency.getInstance(tuple.currency.toString())); } if (tuple.minGroupingDigits != null) { // Oops we don't support this. } if (tuple.useSigDigits != null) { // Oops we don't support this } if (tuple.minSigDigits != null) { // Oops we don't support this } if (tuple.maxSigDigits != null) { // Oops we don't support this } if (tuple.useGrouping != null) { fmt.setGroupingUsed(tuple.useGrouping != 0); } if (tuple.multiplier != null) { fmt.setMultiplier(tuple.multiplier); } if (tuple.roundingIncrement != null) { // Not supported } if (tuple.formatWidth != null) { // Not supported } if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { // Not supported } if (tuple.useScientific != null) { // Not supported } if (tuple.grouping != null) { fmt.setGroupingSize(tuple.grouping); } if (tuple.grouping2 != null) { // Not supported } if (tuple.roundingMode != null) { // Not supported } if (tuple.currencyUsage != null) { // Not supported } if (tuple.minimumExponentDigits != null) { // Not supported } if (tuple.exponentSignAlwaysShown != null) { // Not supported } if (tuple.decimalSeparatorAlwaysShown != null) { fmt.setDecimalSeparatorAlwaysShown( tuple.decimalSeparatorAlwaysShown != 0); } if (tuple.padPosition != null) { // Not supported } if (tuple.positivePrefix != null) { fmt.setPositivePrefix(tuple.positivePrefix); } if (tuple.positiveSuffix != null) { fmt.setPositiveSuffix(tuple.positiveSuffix); } if (tuple.negativePrefix != null) { fmt.setNegativePrefix(tuple.negativePrefix); } if (tuple.negativeSuffix != null) { fmt.setNegativeSuffix(tuple.negativeSuffix); } if (tuple.localizedPattern != null) { fmt.applyLocalizedPattern(tuple.localizedPattern); } // lenient parsing not supported by JDK if (tuple.parseIntegerOnly != null) { fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); } if (tuple.decimalPatternMatchRequired != null) { // Oops, not supported } if (tuple.parseNoExponent != null) { // Oops, not supported for now } } }; @Test public void TestRoundingScientific10542() { DecimalFormat format = new DecimalFormat(""0.00E0""); int[] roundingModes = { BigDecimal.ROUND_CEILING, BigDecimal.ROUND_DOWN, BigDecimal.ROUND_FLOOR, BigDecimal.ROUND_HALF_DOWN, BigDecimal.ROUND_HALF_EVEN, BigDecimal.ROUND_HALF_UP, BigDecimal.ROUND_UP}; String[] descriptions = { ""Round Ceiling"", ""Round Down"", ""Round Floor"", ""Round half down"", ""Round half even"", ""Round half up"", ""Round up""}; double[] values = {-0.003006, -0.003005, -0.003004, 0.003014, 0.003015, 0.003016}; // The order of these expected values correspond to the order of roundingModes and the order of values. String[][] expected = { {""-3.00E-3"", ""-3.00E-3"", ""-3.00E-3"", ""3.02E-3"", ""3.02E-3"", ""3.02E-3""}, {""-3.00E-3"", ""-3.00E-3"", ""-3.00E-3"", ""3.01E-3"", ""3.01E-3"", ""3.01E-3""}, {""-3.01E-3"", ""-3.01E-3"", ""-3.01E-3"", ""3.01E-3"", ""3.01E-3"", ""3.01E-3""}, {""-3.01E-3"", ""-3.00E-3"", ""-3.00E-3"", ""3.01E-3"", ""3.01E-3"", ""3.02E-3""}, {""-3.01E-3"", ""-3.00E-3"", ""-3.00E-3"", ""3.01E-3"", ""3.02E-3"", ""3.02E-3""}, {""-3.01E-3"", ""-3.01E-3"", ""-3.00E-3"", ""3.01E-3"", ""3.02E-3"", ""3.02E-3""}, {""-3.01E-3"", ""-3.01E-3"", ""-3.01E-3"", ""3.02E-3"", ""3.02E-3"", ""3.02E-3""}}; verifyRounding(format, values, expected, roundingModes, descriptions); values = new double[]{-3006.0, -3005, -3004, 3014, 3015, 3016}; // The order of these expected values correspond to the order of roundingModes and the order of values. expected = new String[][]{ {""-3.00E3"", ""-3.00E3"", ""-3.00E3"", ""3.02E3"", ""3.02E3"", ""3.02E3""}, {""-3.00E3"", ""-3.00E3"", ""-3.00E3"", ""3.01E3"", ""3.01E3"", ""3.01E3""}, {""-3.01E3"", ""-3.01E3"", ""-3.01E3"", ""3.01E3"", ""3.01E3"", ""3.01E3""}, {""-3.01E3"", ""-3.00E3"", ""-3.00E3"", ""3.01E3"", ""3.01E3"", ""3.02E3""}, {""-3.01E3"", ""-3.00E3"", ""-3.00E3"", ""3.01E3"", ""3.02E3"", ""3.02E3""}, {""-3.01E3"", ""-3.01E3"", ""-3.00E3"", ""3.01E3"", ""3.02E3"", ""3.02E3""}, {""-3.01E3"", ""-3.01E3"", ""-3.01E3"", ""3.02E3"", ""3.02E3"", ""3.02E3""}}; verifyRounding(format, values, expected, roundingModes, descriptions); values = new double[]{0.0, -0.0}; // The order of these expected values correspond to the order of roundingModes and the order of values. expected = new String[][]{ {""0.00E0"", ""-0.00E0""}, {""0.00E0"", ""-0.00E0""}, {""0.00E0"", ""-0.00E0""}, {""0.00E0"", ""-0.00E0""}, {""0.00E0"", ""-0.00E0""}, {""0.00E0"", ""-0.00E0""}, {""0.00E0"", ""-0.00E0""}}; verifyRounding(format, values, expected, roundingModes, descriptions); values = new double[]{1e25, 1e25 + 1e15, 1e25 - 1e15}; // The order of these expected values correspond to the order of roundingModes and the order of values. expected = new String[][]{ {""1.00E25"", ""1.01E25"", ""1.00E25""}, {""1.00E25"", ""1.00E25"", ""9.99E24""}, {""1.00E25"", ""1.00E25"", ""9.99E24""}, {""1.00E25"", ""1.00E25"", ""1.00E25""}, {""1.00E25"", ""1.00E25"", ""1.00E25""}, {""1.00E25"", ""1.00E25"", ""1.00E25""}, {""1.00E25"", ""1.01E25"", ""1.00E25""}}; verifyRounding(format, values, expected, roundingModes, descriptions); values = new double[]{-1e25, -1e25 + 1e15, -1e25 - 1e15}; // The order of these expected values correspond to the order of roundingModes and the order of values. expected = new String[][]{ {""-1.00E25"", ""-9.99E24"", ""-1.00E25""}, {""-1.00E25"", ""-9.99E24"", ""-1.00E25""}, {""-1.00E25"", ""-1.00E25"", ""-1.01E25""}, {""-1.00E25"", ""-1.00E25"", ""-1.00E25""}, {""-1.00E25"", ""-1.00E25"", ""-1.00E25""}, {""-1.00E25"", ""-1.00E25"", ""-1.00E25""}, {""-1.00E25"", ""-1.00E25"", ""-1.01E25""}}; verifyRounding(format, values, expected, roundingModes, descriptions); values = new double[]{1e-25, 1e-25 + 1e-35, 1e-25 - 1e-35}; // The order of these expected values correspond to the order of roundingModes and the order of values. expected = new String[][]{ {""1.00E-25"", ""1.01E-25"", ""1.00E-25""}, {""1.00E-25"", ""1.00E-25"", ""9.99E-26""}, {""1.00E-25"", ""1.00E-25"", ""9.99E-26""}, {""1.00E-25"", ""1.00E-25"", ""1.00E-25""}, {""1.00E-25"", ""1.00E-25"", ""1.00E-25""}, {""1.00E-25"", ""1.00E-25"", ""1.00E-25""}, {""1.00E-25"", ""1.01E-25"", ""1.00E-25""}}; verifyRounding(format, values, expected, roundingModes, descriptions); values = new double[]{-1e-25, -1e-25 + 1e-35, -1e-25 - 1e-35}; // The order of these expected values correspond to the order of roundingModes and the order of values. expected = new String[][]{ {""-1.00E-25"", ""-9.99E-26"", ""-1.00E-25""}, {""-1.00E-25"", ""-9.99E-26"", ""-1.00E-25""}, {""-1.00E-25"", ""-1.00E-25"", ""-1.01E-25""}, {""-1.00E-25"", ""-1.00E-25"", ""-1.00E-25""}, {""-1.00E-25"", ""-1.00E-25"", ""-1.00E-25""}, {""-1.00E-25"", ""-1.00E-25"", ""-1.00E-25""}, {""-1.00E-25"", ""-1.00E-25"", ""-1.01E-25""}}; verifyRounding(format, values, expected, roundingModes, descriptions); } private void verifyRounding(DecimalFormat format, double[] values, String[][] expected, int[] roundingModes, String[] descriptions) { for (int i = 0; i < roundingModes.length; i++) { format.setRoundingMode(roundingModes[i]); for (int j = 0; j < values.length; j++) { assertEquals(descriptions[i]+"" "" +values[j], expected[i][j], format.format(values[j])); } } } @Test public void Test10419RoundingWith0FractionDigits() { Object[][] data = new Object[][]{ {BigDecimal.ROUND_CEILING, 1.488, ""2""}, {BigDecimal.ROUND_DOWN, 1.588, ""1""}, {BigDecimal.ROUND_FLOOR, 1.588, ""1""}, {BigDecimal.ROUND_HALF_DOWN, 1.5, ""1""}, {BigDecimal.ROUND_HALF_EVEN, 2.5, ""2""}, {BigDecimal.ROUND_HALF_UP, 2.5, ""3""}, {BigDecimal.ROUND_UP, 1.5, ""2""}, }; NumberFormat nff = NumberFormat.getNumberInstance(ULocale.ENGLISH); nff.setMaximumFractionDigits(0); for (Object[] item : data) { nff.setRoundingMode(((Integer) item[0]).intValue()); assertEquals(""Test10419"", item[2], nff.format(item[1])); } } @Test public void TestParseNegativeWithFaLocale() { DecimalFormat parser = (DecimalFormat) NumberFormat.getInstance(new ULocale(""fa"")); try { double value = parser.parse(""-0,5"").doubleValue(); assertEquals(""Expect -0.5"", -0.5, value); } catch (ParseException e) { TestFmwk.errln(""Parsing -0.5 should have succeeded.""); } } @Test public void TestParseNegativeWithAlternativeMinusSign() { DecimalFormat parser = (DecimalFormat) NumberFormat.getInstance(new ULocale(""en"")); try { double value = parser.parse(""\u208B0.5"").doubleValue(); assertEquals(""Expect -0.5"", -0.5, value); } catch (ParseException e) { TestFmwk.errln(""Parsing -0.5 should have succeeded.""); } } // Test various patterns @Test public void TestPatterns() { DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US); final String pat[] = { ""#.#"", ""#."", "".#"", ""#"" }; int pat_length = pat.length; final String newpat[] = { ""#0.#"", ""#0."", ""#.0"", ""#"" }; final String num[] = { ""0"", ""0."", "".0"", ""0"" }; for (int i=0; i \"""" + fmt.toPattern() + ""\""""); int v; for (v = 0; v < val_length; ++v) { String s; s = ((NumberFormat) fmt).format(val[v]); logln("" "" + val[v] + "" -format-> "" + s); if (!s.equals(valFormat[v + ival])) errln(""FAIL: Expected "" + valFormat[v + ival]); ParsePosition pos = new ParsePosition(0); double a = fmt.parse(s, pos).doubleValue(); if (pos.getIndex() == s.length()) { logln("" -parse-> "" + Double.toString(a)); // Use epsilon comparison as necessary } else errln(""FAIL: Partial parse ("" + pos.getIndex() + "" chars) -> "" + a); } for (v = 0; v < lval_length; ++v) { String s; s = ((NumberFormat) fmt).format(lval[v]); logln("" "" + lval[v] + ""L -format-> "" + s); if (!s.equals(lvalFormat[v + ilval])) errln(""ERROR: Expected "" + lvalFormat[v + ilval] + "" Got: "" + s); ParsePosition pos = new ParsePosition(0); long a = 0; Number A = fmt.parse(s, pos); if (A != null) { a = A.longValue(); if (pos.getIndex() == s.length()) { logln("" -parse-> "" + a); if (a != lvalParse[v + ilval]) errln(""FAIL: Expected "" + lvalParse[v + ilval]); } else errln(""FAIL: Partial parse ("" + pos.getIndex() + "" chars) -> "" + Long.toString(a)); } else { errln(""Fail to parse the string: "" + s); } } ival += val_length; ilval += lval_length; } } // Test the handling of quotes @Test public void TestQuotes() { StringBuffer pat; DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US); pat = new StringBuffer(""a'fo''o'b#""); DecimalFormat fmt = new DecimalFormat(pat.toString(), sym); String s = ((NumberFormat)fmt).format(123); logln(""Pattern \"""" + pat + ""\""""); logln("" Format 123 . "" + s); if (!s.equals(""afo'ob123"")) errln(""FAIL: Expected afo'ob123""); s =""""; pat = new StringBuffer(""a''b#""); fmt = new DecimalFormat(pat.toString(), sym); s = ((NumberFormat)fmt).format(123); logln(""Pattern \"""" + pat + ""\""""); logln("" Format 123 . "" + s); if (!s.equals(""a'b123"")) errln(""FAIL: Expected a'b123""); } @Test public void TestParseCurrencyTrailingSymbol() { // see sun bug 4709840 NumberFormat fmt = NumberFormat.getCurrencyInstance(Locale.GERMANY); float val = 12345.67f; String str = fmt.format(val); logln(""val: "" + val + "" str: "" + str); try { Number num = fmt.parse(str); logln(""num: "" + num); } catch (ParseException e) { errln(""parse of '"" + str + ""' threw exception: "" + e); } } /** * Test the handling of the currency symbol in patterns. **/ @Test public void TestCurrencySign() { DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US); StringBuffer pat = new StringBuffer(""""); char currency = 0x00A4; // ""\xA4#,##0.00;-\xA4#,##0.00"" pat.append(currency).append(""#,##0.00;-"").append(currency).append(""#,##0.00""); DecimalFormat fmt = new DecimalFormat(pat.toString(), sym); String s = ((NumberFormat) fmt).format(1234.56); pat = new StringBuffer(); logln(""Pattern \"""" + fmt.toPattern() + ""\""""); logln("" Format "" + 1234.56 + "" . "" + s); assertEquals(""symbol, pos"", ""$1,234.56"", s); s = ((NumberFormat) fmt).format(-1234.56); logln("" Format "" + Double.toString(-1234.56) + "" . "" + s); assertEquals(""symbol, neg"", ""-$1,234.56"", s); pat.setLength(0); // ""\xA4\xA4 #,##0.00;\xA4\xA4 -#,##0.00"" pat.append(currency).append(currency).append("" #,##0.00;"").append(currency).append(currency).append("" -#,##0.00""); fmt = new DecimalFormat(pat.toString(), sym); s = ((NumberFormat) fmt).format(1234.56); logln(""Pattern \"""" + fmt.toPattern() + ""\""""); logln("" Format "" + Double.toString(1234.56) + "" . "" + s); assertEquals(""name, pos"", ""USD 1,234.56"", s); s = ((NumberFormat) fmt).format(-1234.56); logln("" Format "" + Double.toString(-1234.56) + "" . "" + s); assertEquals(""name, neg"", ""USD -1,234.56"", s); } @Test public void TestSpaceParsing() { // the data are: // the string to be parsed, parsed position, parsed error index String[][] DATA = { {""$124"", ""4"", ""-1""}, {""$124 $124"", ""4"", ""-1""}, {""$124 "", ""4"", ""-1""}, {""$ 124 "", ""5"", ""-1""}, {""$\u00A0124 "", ""5"", ""-1""}, {"" $ 124 "", ""0"", ""0""}, // TODO: need to handle space correctly {""124$"", ""0"", ""3""}, // TODO: need to handle space correctly // {""124 $"", ""5"", ""-1""}, TODO: OK or NOT? {""124 $"", ""0"", ""3""}, }; NumberFormat foo = NumberFormat.getCurrencyInstance(); for (int i = 0; i < DATA.length; ++i) { ParsePosition parsePosition = new ParsePosition(0); String stringToBeParsed = DATA[i][0]; int parsedPosition = Integer.parseInt(DATA[i][1]); int errorIndex = Integer.parseInt(DATA[i][2]); try { Number result = foo.parse(stringToBeParsed, parsePosition); if (parsePosition.getIndex() != parsedPosition || parsePosition.getErrorIndex() != errorIndex) { errln(""FAILED parse "" + stringToBeParsed + ""; parse position: "" + parsePosition.getIndex() + ""; error position: "" + parsePosition.getErrorIndex()); } if (parsePosition.getErrorIndex() == -1 && result.doubleValue() != 124) { errln(""FAILED parse "" + stringToBeParsed + ""; value "" + result.doubleValue()); } } catch (Exception e) { errln(""FAILED "" + e.toString()); } } } @Test public void TestMultiCurrencySign() { String[][] DATA = { // the fields in the following test are: // locale, // currency pattern (with negative pattern), // currency number to be formatted, // currency format using currency symbol name, such as ""$"" for USD, // currency format using currency ISO name, such as ""USD"", // currency format using plural name, such as ""US dollars"". // for US locale {""en_US"", ""\u00A4#,##0.00;-\u00A4#,##0.00"", ""1234.56"", ""$1,234.56"", ""USD1,234.56"", ""US dollars1,234.56""}, {""en_US"", ""\u00A4#,##0.00;-\u00A4#,##0.00"", ""-1234.56"", ""-$1,234.56"", ""-USD1,234.56"", ""-US dollars1,234.56""}, {""en_US"", ""\u00A4#,##0.00;-\u00A4#,##0.00"", ""1"", ""$1.00"", ""USD1.00"", ""US dollars1.00""}, // for CHINA locale {""zh_CN"", ""\u00A4#,##0.00;(\u00A4#,##0.00)"", ""1234.56"", ""\uFFE51,234.56"", ""CNY1,234.56"", ""\u4EBA\u6C11\u5E011,234.56""}, {""zh_CN"", ""\u00A4#,##0.00;(\u00A4#,##0.00)"", ""-1234.56"", ""(\uFFE51,234.56)"", ""(CNY1,234.56)"", ""(\u4EBA\u6C11\u5E011,234.56)""}, {""zh_CN"", ""\u00A4#,##0.00;(\u00A4#,##0.00)"", ""1"", ""\uFFE51.00"", ""CNY1.00"", ""\u4EBA\u6C11\u5E011.00""} }; String doubleCurrencyStr = ""\u00A4\u00A4""; String tripleCurrencyStr = ""\u00A4\u00A4\u00A4""; for (int i=0; i "" + s); } else { logln(""FAIL: 1.50 x "" + locale + "" => "" + s + "", expected "" + DATA[i+3]); } } // format currency with CurrencyAmount for (int i=0; i "" + sCurr); } else { errln(""FAIL: 1.50 x "" + locale + "" => "" + sCurr + "", expected "" + DATA[i+3]); } } //Cover MeasureFormat.getCurrencyFormat() ULocale save = ULocale.getDefault(); ULocale.setDefault(ULocale.US); MeasureFormat curFmt = MeasureFormat.getCurrencyFormat(); String strBuf = curFmt.format(new CurrencyAmount(new Float(1234.56), Currency.getInstance(""USD""))); try { CurrencyAmount parsedVal = (CurrencyAmount)curFmt.parseObject(strBuf); Number val = parsedVal.getNumber(); if (!val.equals(new BigDecimal(""1234.56""))) { errln(""FAIL: getCurrencyFormat of default locale (en_US) failed roundtripping the number. val="" + val); } if (!parsedVal.getCurrency().equals(Currency.getInstance(""USD""))) { errln(""FAIL: getCurrencyFormat of default locale (en_US) failed roundtripping the currency""); } } catch (ParseException e) { errln(""FAIL: "" + e.getMessage()); } ULocale.setDefault(save); } @Test public void TestCurrencyIsoPluralFormat() { String[][] DATA = { // the data are: // locale, // currency amount to be formatted, // currency ISO code to be formatted, // format result using CURRENCYSTYLE, // format result using ISOCURRENCYSTYLE, // format result using PLURALCURRENCYSTYLE, {""en_US"", ""1"", ""USD"", ""$1.00"", ""USD1.00"", ""1.00 US dollars""}, {""en_US"", ""1234.56"", ""USD"", ""$1,234.56"", ""USD1,234.56"", ""1,234.56 US dollars""}, {""en_US"", ""-1234.56"", ""USD"", ""-$1,234.56"", ""-USD1,234.56"", ""-1,234.56 US dollars""}, {""zh_CN"", ""1"", ""USD"", ""US$1.00"", ""USD1.00"", ""1.00美元""}, {""zh_CN"", ""1234.56"", ""USD"", ""US$1,234.56"", ""USD1,234.56"", ""1,234.56美元""}, {""zh_CN"", ""1"", ""CNY"", ""¥1.00"", ""CNY1.00"", ""1.00人民币""}, {""zh_CN"", ""1234.56"", ""CNY"", ""¥1,234.56"", ""CNY1,234.56"", ""1,234.56人民币""}, {""ru_RU"", ""1"", ""RUB"", ""1,00 \u20BD"", ""1,00 RUB"", ""1,00 российского рубля""}, {""ru_RU"", ""2"", ""RUB"", ""2,00 \u20BD"", ""2,00 RUB"", ""2,00 российского рубля""}, {""ru_RU"", ""5"", ""RUB"", ""5,00 \u20BD"", ""5,00 RUB"", ""5,00 российского рубля""}, // test locale without currency information {""root"", ""-1.23"", ""USD"", ""-US$ 1.23"", ""-USD 1.23"", ""-1.23 USD""}, {""root@numbers=latn"", ""-1.23"", ""USD"", ""-US$ 1.23"", ""-USD 1.23"", ""-1.23 USD""}, // ensure that the root locale is still used with modifiers {""root@numbers=arab"", ""-1.23"", ""USD"", ""\u061C-\u0661\u066B\u0662\u0663\u00A0US$"", ""\u061C-\u0661\u066B\u0662\u0663\u00A0USD"", ""\u061C-\u0661\u066B\u0662\u0663 USD""}, // ensure that the root locale is still used with modifiers {""es_AR"", ""1"", ""INR"", ""INR\u00A01,00"", ""INR\u00A01,00"", ""1,00 rupia india""}, {""ar_EG"", ""1"", ""USD"", ""١٫٠٠\u00A0US$"", ""١٫٠٠\u00A0USD"", ""١٫٠٠ دولار أمريكي""}, }; for (int i=0; i "" + a + ""; x 1.125 => "" + b); } // Make sure EURO currency formats have exactly 2 fraction digits if (nf instanceof DecimalFormat) { Currency curr = ((DecimalFormat) nf).getCurrency(); if (curr != null && ""EUR"".equals(curr.getCurrencyCode())) { if (min != 2 || max != 2) { String a = nf.format(1.0); errln(""FAIL: "" + locs[i] + "" is a EURO format but it does not have 2 fraction digits; ""+ ""x 1.0 => "" + a); } } } } } /** * Do rudimentary testing of parsing. */ @Test public void TestParse() { String arg = ""0.0""; DecimalFormat format = new DecimalFormat(""00""); double aNumber = 0l; try { aNumber = format.parse(arg).doubleValue(); } catch (ParseException e) { System.out.println(e); } logln(""parse("" + arg + "") = "" + aNumber); } /** * Test proper rounding by the format method. */ @Test public void TestRounding487() { NumberFormat nf = NumberFormat.getInstance(); roundingTest(nf, 0.00159999, 4, ""0.0016""); roundingTest(nf, 0.00995, 4, ""0.01""); roundingTest(nf, 12.3995, 3, ""12.4""); roundingTest(nf, 12.4999, 0, ""12""); roundingTest(nf, - 19.5, 0, ""-20""); } /** * Test the functioning of the secondary grouping value. */ @Test public void TestSecondaryGrouping() { DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US); DecimalFormat f = new DecimalFormat(""#,##,###"", US); expect(f, 123456789L, ""12,34,56,789""); expectPat(f, ""#,##,###""); f.applyPattern(""#,###""); f.setSecondaryGroupingSize(4); expect(f, 123456789L, ""12,3456,789""); expectPat(f, ""#,####,###""); NumberFormat g = NumberFormat.getInstance(new Locale(""hi"", ""IN"")); String out = """"; long l = 1876543210L; out = g.format(l); // expect ""1,87,65,43,210"", but with Hindi digits // 01234567890123 boolean ok = true; if (out.length() != 14) { ok = false; } else { for (int i = 0; i < out.length(); ++i) { boolean expectGroup = false; switch (i) { case 1 : case 4 : case 7 : case 10 : expectGroup = true; break; } // Later -- fix this to get the actual grouping // character from the resource bundle. boolean isGroup = (out.charAt(i) == 0x002C); if (isGroup != expectGroup) { ok = false; break; } } } if (!ok) { errln(""FAIL Expected ""+ l + "" x hi_IN . \""1,87,65,43,210\"" (with Hindi digits), got \"""" + out + ""\""""); } else { logln(""Ok "" + l + "" x hi_IN . \"""" + out + ""\""""); } } /* * Internal test utility. */ private void roundingTest(NumberFormat nf, double x, int maxFractionDigits, final String expected) { nf.setMaximumFractionDigits(maxFractionDigits); String out = nf.format(x); logln(x + "" formats with "" + maxFractionDigits + "" fractional digits to "" + out); if (!out.equals(expected)) errln(""FAIL: Expected "" + expected); } /** * Upgrade to alphaWorks */ @Test public void TestExponent() { DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US); DecimalFormat fmt1 = new DecimalFormat(""0.###E0"", US); DecimalFormat fmt2 = new DecimalFormat(""0.###E+0"", US); int n = 1234; expect2(fmt1, n, ""1.234E3""); expect2(fmt2, n, ""1.234E+3""); expect(fmt1, ""1.234E+3"", n); // Either format should parse ""E+3"" } /** * Upgrade to alphaWorks */ @Test public void TestScientific() { DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US); // Test pattern round-trip final String PAT[] = { ""#E0"", ""0.####E0"", ""00.000E00"", ""##0.####E000"", ""0.###E0;[0.###E0]"" }; int PAT_length = PAT.length; int DIGITS[] = { // min int, max int, min frac, max frac 0, 1, 0, 0, // ""#E0"" 1, 1, 0, 4, // ""0.####E0"" 2, 2, 3, 3, // ""00.000E00"" 1, 3, 0, 4, // ""##0.####E000"" 1, 1, 0, 3, // ""0.###E0;[0.###E0]"" }; for (int i = 0; i < PAT_length; ++i) { String pat = PAT[i]; DecimalFormat df = new DecimalFormat(pat, US); String pat2 = df.toPattern(); if (pat.equals(pat2)) { logln(""Ok Pattern rt \"""" + pat + ""\"" . \"""" + pat2 + ""\""""); } else { errln(""FAIL Pattern rt \"""" + pat + ""\"" . \"""" + pat2 + ""\""""); } // Make sure digit counts match what we expect if (df.getMinimumIntegerDigits() != DIGITS[4 * i] || df.getMaximumIntegerDigits() != DIGITS[4 * i + 1] || df.getMinimumFractionDigits() != DIGITS[4 * i + 2] || df.getMaximumFractionDigits() != DIGITS[4 * i + 3]) { errln(""FAIL \""""+ pat+ ""\"" min/max int; min/max frac = "" + df.getMinimumIntegerDigits() + ""/"" + df.getMaximumIntegerDigits() + "";"" + df.getMinimumFractionDigits() + ""/"" + df.getMaximumFractionDigits() + "", expect "" + DIGITS[4 * i] + ""/"" + DIGITS[4 * i + 1] + "";"" + DIGITS[4 * i + 2] + ""/"" + DIGITS[4 * i + 3]); } } expect2(new DecimalFormat(""#E0"", US), 12345.0, ""1.2345E4""); expect(new DecimalFormat(""0E0"", US), 12345.0, ""1E4""); // pattern of NumberFormat.getScientificInstance(Locale.US) = ""0.######E0"" not ""#E0"" // so result = 1.234568E4 not 1.2345678901E4 //when the pattern problem is finalized, delete comment mark'//' //of the following code expect2(NumberFormat.getScientificInstance(Locale.US), 12345.678901, ""1.2345678901E4""); logln(""Testing NumberFormat.getScientificInstance(ULocale) ...""); expect2(NumberFormat.getScientificInstance(ULocale.US), 12345.678901, ""1.2345678901E4""); expect(new DecimalFormat(""##0.###E0"", US), 12345.0, ""12.34E3""); expect(new DecimalFormat(""##0.###E0"", US), 12345.00001, ""12.35E3""); expect2(new DecimalFormat(""##0.####E0"", US), 12345, ""12.345E3""); // pattern of NumberFormat.getScientificInstance(Locale.US) = ""0.######E0"" not ""#E0"" // so result = 1.234568E4 not 1.2345678901E4 expect2(NumberFormat.getScientificInstance(Locale.FRANCE), 12345.678901, ""1,2345678901E4""); logln(""Testing NumberFormat.getScientificInstance(ULocale) ...""); expect2(NumberFormat.getScientificInstance(ULocale.FRANCE), 12345.678901, ""1,2345678901E4""); expect(new DecimalFormat(""##0.####E0"", US), 789.12345e-9, ""789.12E-9""); expect2(new DecimalFormat(""##0.####E0"", US), 780.e-9, ""780E-9""); expect(new DecimalFormat("".###E0"", US), 45678.0, "".457E5""); expect2(new DecimalFormat("".###E0"", US), 0, "".0E0""); /* expect(new DecimalFormat[] { new DecimalFormat(""#E0"", US), new DecimalFormat(""##E0"", US), new DecimalFormat(""####E0"", US), new DecimalFormat(""0E0"", US), new DecimalFormat(""00E0"", US), new DecimalFormat(""000E0"", US), }, new Long(45678000), new String[] { ""4.5678E7"", ""45.678E6"", ""4567.8E4"", ""5E7"", ""46E6"", ""457E5"", } ); ! ! Unroll this test into individual tests below... ! */ expect2(new DecimalFormat(""#E0"", US), 45678000, ""4.5678E7""); expect2(new DecimalFormat(""##E0"", US), 45678000, ""45.678E6""); expect2(new DecimalFormat(""####E0"", US), 45678000, ""4567.8E4""); expect(new DecimalFormat(""0E0"", US), 45678000, ""5E7""); expect(new DecimalFormat(""00E0"", US), 45678000, ""46E6""); expect(new DecimalFormat(""000E0"", US), 45678000, ""457E5""); /* expect(new DecimalFormat(""###E0"", US, status), new Object[] { new Double(0.0000123), ""12.3E-6"", new Double(0.000123), ""123E-6"", new Double(0.00123), ""1.23E-3"", new Double(0.0123), ""12.3E-3"", new Double(0.123), ""123E-3"", new Double(1.23), ""1.23E0"", new Double(12.3), ""12.3E0"", new Double(123), ""123E0"", new Double(1230), ""1.23E3"", }); ! ! Unroll this test into individual tests below... ! */ expect2(new DecimalFormat(""###E0"", US), 0.0000123, ""12.3E-6""); expect2(new DecimalFormat(""###E0"", US), 0.000123, ""123E-6""); expect2(new DecimalFormat(""###E0"", US), 0.00123, ""1.23E-3""); expect2(new DecimalFormat(""###E0"", US), 0.0123, ""12.3E-3""); expect2(new DecimalFormat(""###E0"", US), 0.123, ""123E-3""); expect2(new DecimalFormat(""###E0"", US), 1.23, ""1.23E0""); expect2(new DecimalFormat(""###E0"", US), 12.3, ""12.3E0""); expect2(new DecimalFormat(""###E0"", US), 123.0, ""123E0""); expect2(new DecimalFormat(""###E0"", US), 1230.0, ""1.23E3""); /* expect(new DecimalFormat(""0.#E+00"", US, status), new Object[] { new Double(0.00012), ""1.2E-04"", new Long(12000), ""1.2E+04"", }); ! ! Unroll this test into individual tests below... ! */ expect2(new DecimalFormat(""0.#E+00"", US), 0.00012, ""1.2E-04""); expect2(new DecimalFormat(""0.#E+00"", US), 12000, ""1.2E+04""); } /** * Upgrade to alphaWorks */ @Test public void TestPad() { DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US); expect2(new DecimalFormat(""*^##.##"", US), 0, ""^^^^0""); expect2(new DecimalFormat(""*^##.##"", US), -1.3, ""^-1.3""); expect2( new DecimalFormat(""##0.0####E0*_ 'g-m/s^2'"", US), 0, ""0.0E0______ g-m/s^2""); expect( new DecimalFormat(""##0.0####E0*_ 'g-m/s^2'"", US), 1.0 / 3, ""333.333E-3_ g-m/s^2""); expect2(new DecimalFormat(""##0.0####*_ 'g-m/s^2'"", US), 0, ""0.0______ g-m/s^2""); expect( new DecimalFormat(""##0.0####*_ 'g-m/s^2'"", US), 1.0 / 3, ""0.33333__ g-m/s^2""); // Test padding before a sign final String formatStr = ""*x#,###,###,##0.0#;*x(###,###,##0.0#)""; expect2(new DecimalFormat(formatStr, US), -10, ""xxxxxxxxxx(10.0)""); expect2(new DecimalFormat(formatStr, US), -1000, ""xxxxxxx(1,000.0)""); expect2(new DecimalFormat(formatStr, US), -1000000, ""xxx(1,000,000.0)""); expect2(new DecimalFormat(formatStr, US), -100.37, ""xxxxxxxx(100.37)""); expect2(new DecimalFormat(formatStr, US), -10456.37, ""xxxxx(10,456.37)""); expect2(new DecimalFormat(formatStr, US), -1120456.37, ""xx(1,120,456.37)""); expect2(new DecimalFormat(formatStr, US), -112045600.37, ""(112,045,600.37)""); expect2(new DecimalFormat(formatStr, US), -1252045600.37, ""(1,252,045,600.37)""); expect2(new DecimalFormat(formatStr, US), 10, ""xxxxxxxxxxxx10.0""); expect2(new DecimalFormat(formatStr, US), 1000, ""xxxxxxxxx1,000.0""); expect2(new DecimalFormat(formatStr, US), 1000000, ""xxxxx1,000,000.0""); expect2(new DecimalFormat(formatStr, US), 100.37, ""xxxxxxxxxx100.37""); expect2(new DecimalFormat(formatStr, US), 10456.37, ""xxxxxxx10,456.37""); expect2(new DecimalFormat(formatStr, US), 1120456.37, ""xxxx1,120,456.37""); expect2(new DecimalFormat(formatStr, US), 112045600.37, ""xx112,045,600.37""); expect2(new DecimalFormat(formatStr, US), 10252045600.37, ""10,252,045,600.37""); // Test padding between a sign and a number final String formatStr2 = ""#,###,###,##0.0#*x;(###,###,##0.0#*x)""; expect2(new DecimalFormat(formatStr2, US), -10, ""(10.0xxxxxxxxxx)""); expect2(new DecimalFormat(formatStr2, US), -1000, ""(1,000.0xxxxxxx)""); expect2(new DecimalFormat(formatStr2, US), -1000000, ""(1,000,000.0xxx)""); expect2(new DecimalFormat(formatStr2, US), -100.37, ""(100.37xxxxxxxx)""); expect2(new DecimalFormat(formatStr2, US), -10456.37, ""(10,456.37xxxxx)""); expect2(new DecimalFormat(formatStr2, US), -1120456.37, ""(1,120,456.37xx)""); expect2(new DecimalFormat(formatStr2, US), -112045600.37, ""(112,045,600.37)""); expect2(new DecimalFormat(formatStr2, US), -1252045600.37, ""(1,252,045,600.37)""); expect2(new DecimalFormat(formatStr2, US), 10, ""10.0xxxxxxxxxxxx""); expect2(new DecimalFormat(formatStr2, US), 1000, ""1,000.0xxxxxxxxx""); expect2(new DecimalFormat(formatStr2, US), 1000000, ""1,000,000.0xxxxx""); expect2(new DecimalFormat(formatStr2, US), 100.37, ""100.37xxxxxxxxxx""); expect2(new DecimalFormat(formatStr2, US), 10456.37, ""10,456.37xxxxxxx""); expect2(new DecimalFormat(formatStr2, US), 1120456.37, ""1,120,456.37xxxx""); expect2(new DecimalFormat(formatStr2, US), 112045600.37, ""112,045,600.37xx""); expect2(new DecimalFormat(formatStr2, US), 10252045600.37, ""10,252,045,600.37""); //testing the setPadCharacter(UnicodeString) and getPadCharacterString() DecimalFormat fmt = new DecimalFormat(""#"", US); char padString = 'P'; fmt.setPadCharacter(padString); expectPad(fmt, ""*P##.##"", DecimalFormat.PAD_BEFORE_PREFIX, 5, padString); fmt.setPadCharacter('^'); expectPad(fmt, ""*^#"", DecimalFormat.PAD_BEFORE_PREFIX, 1, '^'); //commented untill implementation is complete /* fmt.setPadCharacter((UnicodeString)""^^^""); expectPad(fmt, ""*^^^#"", DecimalFormat.kPadBeforePrefix, 3, (UnicodeString)""^^^""); padString.remove(); padString.append((UChar)0x0061); padString.append((UChar)0x0302); fmt.setPadCharacter(padString); UChar patternChars[]={0x002a, 0x0061, 0x0302, 0x0061, 0x0302, 0x0023, 0x0000}; UnicodeString pattern(patternChars); expectPad(fmt, pattern , DecimalFormat.kPadBeforePrefix, 4, padString); */ } /** * Upgrade to alphaWorks */ @Test public void TestPatterns2() { DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US); DecimalFormat fmt = new DecimalFormat(""#"", US); char hat = 0x005E; /*^*/ expectPad(fmt, ""*^#"", DecimalFormat.PAD_BEFORE_PREFIX, 1, hat); expectPad(fmt, ""$*^#"", DecimalFormat.PAD_AFTER_PREFIX, 2, hat); expectPad(fmt, ""#*^"", DecimalFormat.PAD_BEFORE_SUFFIX, 1, hat); expectPad(fmt, ""#$*^"", DecimalFormat.PAD_AFTER_SUFFIX, 2, hat); expectPad(fmt, ""$*^$#"", -1); expectPad(fmt, ""#$*^$"", -1); expectPad(fmt, ""'pre'#,##0*x'post'"", DecimalFormat.PAD_BEFORE_SUFFIX, 12, (char) 0x0078 /*x*/); expectPad(fmt, ""''#0*x"", DecimalFormat.PAD_BEFORE_SUFFIX, 3, (char) 0x0078 /*x*/); expectPad(fmt, ""'I''ll'*a###.##"", DecimalFormat.PAD_AFTER_PREFIX, 10, (char) 0x0061 /*a*/); fmt.applyPattern(""AA#,##0.00ZZ""); fmt.setPadCharacter(hat); fmt.setFormatWidth(10); fmt.setPadPosition(DecimalFormat.PAD_BEFORE_PREFIX); expectPat(fmt, ""*^AA#,##0.00ZZ""); fmt.setPadPosition(DecimalFormat.PAD_BEFORE_SUFFIX); expectPat(fmt, ""AA#,##0.00*^ZZ""); fmt.setPadPosition(DecimalFormat.PAD_AFTER_SUFFIX); expectPat(fmt, ""AA#,##0.00ZZ*^""); // 12 3456789012 String exp = ""AA*^#,##0.00ZZ""; fmt.setFormatWidth(12); fmt.setPadPosition(DecimalFormat.PAD_AFTER_PREFIX); expectPat(fmt, exp); fmt.setFormatWidth(13); // 12 34567890123 expectPat(fmt, ""AA*^##,##0.00ZZ""); fmt.setFormatWidth(14); // 12 345678901234 expectPat(fmt, ""AA*^###,##0.00ZZ""); fmt.setFormatWidth(15); // 12 3456789012345 expectPat(fmt, ""AA*^####,##0.00ZZ""); // This is the interesting case fmt.setFormatWidth(16); // 12 34567890123456 expectPat(fmt, ""AA*^#,###,##0.00ZZ""); } @Test public void TestRegistration() { final ULocale SRC_LOC = ULocale.FRANCE; final ULocale SWAP_LOC = ULocale.US; class TestFactory extends SimpleNumberFormatFactory { NumberFormat currencyStyle; TestFactory() { super(SRC_LOC, true); currencyStyle = NumberFormat.getIntegerInstance(SWAP_LOC); } @Override public NumberFormat createFormat(ULocale loc, int formatType) { if (formatType == FORMAT_CURRENCY) { return currencyStyle; } return null; } } NumberFormat f0 = NumberFormat.getIntegerInstance(SWAP_LOC); NumberFormat f1 = NumberFormat.getIntegerInstance(SRC_LOC); NumberFormat f2 = NumberFormat.getCurrencyInstance(SRC_LOC); Object key = NumberFormat.registerFactory(new TestFactory()); NumberFormat f3 = NumberFormat.getCurrencyInstance(SRC_LOC); NumberFormat f4 = NumberFormat.getIntegerInstance(SRC_LOC); NumberFormat.unregister(key); // restore for other tests NumberFormat f5 = NumberFormat.getCurrencyInstance(SRC_LOC); float n = 1234.567f; logln(""f0 swap int: "" + f0.format(n)); logln(""f1 src int: "" + f1.format(n)); logln(""f2 src cur: "" + f2.format(n)); logln(""f3 reg cur: "" + f3.format(n)); logln(""f4 reg int: "" + f4.format(n)); logln(""f5 unreg cur: "" + f5.format(n)); if (!f3.format(n).equals(f0.format(n))) { errln(""registered service did not match""); } if (!f4.format(n).equals(f1.format(n))) { errln(""registered service did not inherit""); } if (!f5.format(n).equals(f2.format(n))) { errln(""unregistered service did not match original""); } } @Test public void TestScientific2() { // jb 2552 DecimalFormat fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(); Number num = new Double(12.34); expect(fmt, num, ""$12.34""); fmt.setScientificNotation(true); expect(fmt, num, ""$1.23E1""); fmt.setScientificNotation(false); expect(fmt, num, ""$12.34""); } @Test public void TestScientificGrouping() { // jb 2552 DecimalFormat fmt = new DecimalFormat(""###.##E0""); expect(fmt, .01234, ""12.3E-3""); expect(fmt, .1234, ""123E-3""); expect(fmt, 1.234, ""1.23E0""); expect(fmt, 12.34, ""12.3E0""); expect(fmt, 123.4, ""123E0""); expect(fmt, 1234, ""1.23E3""); } // additional coverage tests // sigh, can't have static inner classes, why not? static final class PI extends Number { /** * For serialization */ private static final long serialVersionUID = -305601227915602172L; private PI() {} @Override public int intValue() { return (int)Math.PI; } @Override public long longValue() { return (long)Math.PI; } @Override public float floatValue() { return (float)Math.PI; } @Override public double doubleValue() { return Math.PI; } @Override public byte byteValue() { return (byte)Math.PI; } @Override public short shortValue() { return (short)Math.PI; } public static final Number INSTANCE = new PI(); } @Test public void TestCoverage() { NumberFormat fmt = NumberFormat.getNumberInstance(); // default locale logln(fmt.format(new BigInteger(""1234567890987654321234567890987654321"", 10))); fmt = NumberFormat.getScientificInstance(); // default locale logln(fmt.format(PI.INSTANCE)); try { logln(fmt.format(""12345"")); errln(""numberformat of string did not throw exception""); } catch (Exception e) { logln(""PASS: numberformat of string failed as expected""); } int hash = fmt.hashCode(); logln(""hash code "" + hash); logln(""compare to string returns: "" + fmt.equals("""")); // For ICU 2.6 - alan DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US); DecimalFormat df = new DecimalFormat(""'*&'' '\u00A4' ''&*' #,##0.00"", US); df.setCurrency(Currency.getInstance(""INR"")); expect2(df, 1.0, ""*&' \u20B9 '&* 1.00""); expect2(df, -2.0, ""-*&' \u20B9 '&* 2.00""); df.applyPattern(""#,##0.00 '*&'' '\u00A4' ''&*'""); expect2(df, 2.0, ""2.00 *&' \u20B9 '&*""); expect2(df, -1.0, ""-1.00 *&' \u20B9 '&*""); java.math.BigDecimal r; r = df.getRoundingIncrement(); if (r != null) { errln(""FAIL: rounding = "" + r + "", expect null""); } if (df.isScientificNotation()) { errln(""FAIL: isScientificNotation = true, expect false""); } df.applyPattern(""0.00000""); df.setScientificNotation(true); if (!df.isScientificNotation()) { errln(""FAIL: isScientificNotation = false, expect true""); } df.setMinimumExponentDigits((byte)2); if (df.getMinimumExponentDigits() != 2) { errln(""FAIL: getMinimumExponentDigits = "" + df.getMinimumExponentDigits() + "", expect 2""); } df.setExponentSignAlwaysShown(true); if (!df.isExponentSignAlwaysShown()) { errln(""FAIL: isExponentSignAlwaysShown = false, expect true""); } df.setSecondaryGroupingSize(0); if (df.getSecondaryGroupingSize() != 0) { errln(""FAIL: getSecondaryGroupingSize = "" + df.getSecondaryGroupingSize() + "", expect 0""); } expect2(df, 3.14159, ""3.14159E+00""); // DecimalFormatSymbols#getInstance DecimalFormatSymbols decsym1 = DecimalFormatSymbols.getInstance(); DecimalFormatSymbols decsym2 = new DecimalFormatSymbols(); if (!decsym1.equals(decsym2)) { errln(""FAIL: DecimalFormatSymbols returned by getInstance()"" + ""does not match new DecimalFormatSymbols().""); } decsym1 = DecimalFormatSymbols.getInstance(Locale.JAPAN); decsym2 = DecimalFormatSymbols.getInstance(ULocale.JAPAN); if (!decsym1.equals(decsym2)) { errln(""FAIL: DecimalFormatSymbols returned by getInstance(Locale.JAPAN)"" + ""does not match the one returned by getInstance(ULocale.JAPAN).""); } // DecimalFormatSymbols#getAvailableLocales/#getAvailableULocales Locale[] allLocales = DecimalFormatSymbols.getAvailableLocales(); if (allLocales.length == 0) { errln(""FAIL: Got a empty list for DecimalFormatSymbols.getAvailableLocales""); } else { logln(""PASS: "" + allLocales.length + "" available locales returned by DecimalFormatSymbols.getAvailableLocales""); } ULocale[] allULocales = DecimalFormatSymbols.getAvailableULocales(); if (allULocales.length == 0) { errln(""FAIL: Got a empty list for DecimalFormatSymbols.getAvailableLocales""); } else { logln(""PASS: "" + allULocales.length + "" available locales returned by DecimalFormatSymbols.getAvailableULocales""); } } @Test public void TestWhiteSpaceParsing() { DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US); DecimalFormat fmt = new DecimalFormat(""a b#0c "", US); int n = 1234; expect(fmt, ""a b1234c "", n); expect(fmt, ""a b1234c "", n); } /** * Test currencies whose display name is a ChoiceFormat. */ @Test public void TestComplexCurrency() { // CLDR No Longer uses complex currency symbols. // Skipping this test. // Locale loc = new Locale(""kn"", ""IN"", """"); // NumberFormat fmt = NumberFormat.getCurrencyInstance(loc); // expect2(fmt, 1.0, ""Re.\u00a01.00""); // expect(fmt, 1.001, ""Re.\u00a01.00""); // tricky // expect2(fmt, 12345678.0, ""Rs.\u00a01,23,45,678.00""); // expect2(fmt, 0.5, ""Rs.\u00a00.50""); // expect2(fmt, -1.0, ""-Re.\u00a01.00""); // expect2(fmt, -10.0, ""-Rs.\u00a010.00""); } @Test public void TestCurrencyKeyword() { ULocale locale = new ULocale(""th_TH@currency=QQQ""); NumberFormat format = NumberFormat.getCurrencyInstance(locale); String result = format.format(12.34f); if (!""QQQ12.34"".equals(result)) { errln(""got unexpected currency: "" + result); } } /** * Test alternate numbering systems */ @Test public void TestNumberingSystems() { class TestNumberingSystemItem { private final String localeName; private final double value; private final boolean isRBNF; private final String expectedResult; TestNumberingSystemItem(String loc, double val, boolean rbnf, String exp) { localeName = loc; value = val; isRBNF = rbnf; expectedResult = exp; } } final TestNumberingSystemItem[] DATA = { new TestNumberingSystemItem( ""en_US@numbers=thai"", 1234.567, false, ""\u0e51,\u0e52\u0e53\u0e54.\u0e55\u0e56\u0e57"" ), new TestNumberingSystemItem( ""en_US@numbers=thai"", 1234.567, false, ""\u0E51,\u0E52\u0E53\u0E54.\u0E55\u0E56\u0E57"" ), new TestNumberingSystemItem( ""en_US@numbers=hebr"", 5678.0, true, ""\u05D4\u05F3\u05EA\u05E8\u05E2\u05F4\u05D7"" ), new TestNumberingSystemItem( ""en_US@numbers=arabext"", 1234.567, false, ""\u06F1\u066c\u06F2\u06F3\u06F4\u066b\u06F5\u06F6\u06F7"" ), new TestNumberingSystemItem( ""de_DE@numbers=foobar"", 1234.567, false, ""1.234,567"" ), new TestNumberingSystemItem( ""ar_EG"", 1234.567, false, ""\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666\u0667"" ), new TestNumberingSystemItem( ""th_TH@numbers=traditional"", 1234.567, false, ""\u0E51,\u0E52\u0E53\u0E54.\u0E55\u0E56\u0E57"" ), // fall back to native per TR35 new TestNumberingSystemItem( ""ar_MA"", 1234.567, false, ""1.234,567"" ), new TestNumberingSystemItem( ""en_US@numbers=hanidec"", 1234.567, false, ""\u4e00,\u4e8c\u4e09\u56db.\u4e94\u516d\u4e03"" ), new TestNumberingSystemItem( ""ta_IN@numbers=native"", 1234.567, false, ""\u0BE7,\u0BE8\u0BE9\u0BEA.\u0BEB\u0BEC\u0BED"" ), new TestNumberingSystemItem( ""ta_IN@numbers=traditional"", 1235.0, true, ""\u0BF2\u0BE8\u0BF1\u0BE9\u0BF0\u0BEB"" ), new TestNumberingSystemItem( ""ta_IN@numbers=finance"", 1234.567, false, ""1,234.567"" ), // fall back to default per TR35 new TestNumberingSystemItem( ""zh_TW@numbers=native"", 1234.567, false, ""\u4e00,\u4e8c\u4e09\u56db.\u4e94\u516d\u4e03"" ), new TestNumberingSystemItem( ""zh_TW@numbers=traditional"", 1234.567, true, ""\u4E00\u5343\u4E8C\u767E\u4E09\u5341\u56DB\u9EDE\u4E94\u516D\u4E03"" ), new TestNumberingSystemItem( ""zh_TW@numbers=finance"", 1234.567, true, ""\u58F9\u4EDF\u8CB3\u4F70\u53C3\u62FE\u8086\u9EDE\u4F0D\u9678\u67D2"" ) }; for (TestNumberingSystemItem item : DATA) { ULocale loc = new ULocale(item.localeName); NumberFormat fmt = NumberFormat.getInstance(loc); if (item.isRBNF) { expect3(fmt,item.value,item.expectedResult); } else { expect2(fmt,item.value,item.expectedResult); } } } // Coverage tests for methods not being called otherwise. @Test public void TestNumberingSystemCoverage() { // Test getAvaliableNames String[] availableNames = NumberingSystem.getAvailableNames(); if (availableNames == null || availableNames.length <= 0) { errln(""ERROR: NumberingSystem.getAvailableNames() returned a null or empty array.""); } else { boolean latnFound = false; for (String name : availableNames){ if (""latn"".equals(name)) { latnFound = true; break; } } if (!latnFound) { errln(""ERROR: 'latn' numbering system not found on NumberingSystem.getAvailableNames().""); } } // Test NumberingSystem.getInstance() NumberingSystem ns1 = NumberingSystem.getInstance(); if (ns1 == null || ns1.isAlgorithmic()) { errln(""ERROR: NumberingSystem.getInstance() returned a null or invalid NumberingSystem""); } // Test NumberingSystem.getInstance(int,boolean,String) /* Parameters used: the ones used in the default constructor * radix = 10; * algorithmic = false; * desc = ""0123456789""; */ NumberingSystem ns2 = NumberingSystem.getInstance(10, false, ""0123456789""); if (ns2 == null || ns2.isAlgorithmic()) { errln(""ERROR: NumberingSystem.getInstance(int,boolean,String) returned a null or invalid NumberingSystem""); } // Test NumberingSystem.getInstance(Locale) NumberingSystem ns3 = NumberingSystem.getInstance(Locale.ENGLISH); if (ns3 == null || ns3.isAlgorithmic()) { errln(""ERROR: NumberingSystem.getInstance(Locale) returned a null or invalid NumberingSystem""); } } @Test public void Test6816() { Currency cur1 = Currency.getInstance(new Locale(""und"", ""PH"")); NumberFormat nfmt = NumberFormat.getCurrencyInstance(new Locale(""und"", ""PH"")); DecimalFormatSymbols decsym = ((DecimalFormat)nfmt).getDecimalFormatSymbols(); Currency cur2 = decsym.getCurrency(); if ( !cur1.getCurrencyCode().equals(""PHP"") || !cur2.getCurrencyCode().equals(""PHP"")) { errln(""FAIL: Currencies should match PHP: cur1 = ""+cur1.getCurrencyCode()+""; cur2 = ""+cur2.getCurrencyCode()); } } @Test public void TestThreadedFormat() { class FormatTask implements Runnable { DecimalFormat fmt; StringBuffer buf; boolean inc; float num; FormatTask(DecimalFormat fmt, int index) { this.fmt = fmt; this.buf = new StringBuffer(); this.inc = (index & 0x1) == 0; this.num = inc ? 0 : 10000; } @Override public void run() { if (inc) { while (num < 10000) { buf.append(fmt.format(num) + ""\n""); num += 3.14159; } } else { while (num > 0) { buf.append(fmt.format(num) + ""\n""); num -= 3.14159; } } } String result() { return buf.toString(); } } DecimalFormat fmt = new DecimalFormat(""0.####""); FormatTask[] tasks = new FormatTask[8]; for (int i = 0; i < tasks.length; ++i) { tasks[i] = new FormatTask(fmt, i); } TestUtil.runUntilDone(tasks); for (int i = 2; i < tasks.length; i++) { String str1 = tasks[i].result(); String str2 = tasks[i-2].result(); if (!str1.equals(str2)) { System.out.println(""mismatch at "" + i); System.out.println(str1); System.out.println(str2); errln(""decimal format thread mismatch""); break; } str1 = str2; } } @Test public void TestPerMill() { DecimalFormat fmt = new DecimalFormat(""###.###\u2030""); assertEquals(""0.4857 x ###.###\u2030"", ""485.7\u2030"", fmt.format(0.4857)); DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.ENGLISH); sym.setPerMill('m'); DecimalFormat fmt2 = new DecimalFormat("""", sym); fmt2.applyLocalizedPattern(""###.###m""); assertEquals(""0.4857 x ###.###m"", ""485.7m"", fmt2.format(0.4857)); } @Test public void TestIllegalPatterns() { // Test cases: // Prefix with ""-:"" for illegal patterns // Prefix with ""+:"" for legal patterns String DATA[] = { // Unquoted special characters in the suffix are illegal ""-:000.000|###"", ""+:000.000'|###'"", }; for (int i=0; i /*1*/ ""loc="", // /*2*/ ""f:"", // /*3*/ ""fp:"", // /*4*/ ""rt:"", // <(exp.) number> <(exp.) string> /*5*/ ""p:"", // /*6*/ ""perr:"", // /*7*/ ""pat:"", // /*8*/ ""fpc:"", // /*9*/ ""strict="", // true or false }; @SuppressWarnings(""resource"") // InputStream is will be closed by the ResourceReader. @Test public void TestCases() { String caseFileName = ""NumberFormatTestCases.txt""; java.io.InputStream is = NumberFormatTest.class.getResourceAsStream(caseFileName); ResourceReader reader = new ResourceReader(is, caseFileName, ""utf-8""); TokenIterator tokens = new TokenIterator(reader); Locale loc = new Locale(""en"", ""US"", """"); DecimalFormat ref = null, fmt = null; MeasureFormat mfmt = null; String pat = null, str = null, mloc = null; boolean strict = false; try { for (;;) { String tok = tokens.next(); if (tok == null) { break; } String where = ""("" + tokens.getLineNumber() + "") ""; int cmd = keywordIndex(tok); switch (cmd) { case 0: // ref= ref = new DecimalFormat(tokens.next(), new DecimalFormatSymbols(Locale.US)); ref.setParseStrict(strict); logln(""Setting reference pattern to:\t"" + ref); break; case 1: // loc= loc = LocaleUtility.getLocaleFromName(tokens.next()); pat = ((DecimalFormat) NumberFormat.getInstance(loc)).toPattern(); logln(""Setting locale to:\t"" + loc + "", \tand pattern to:\t"" + pat); break; case 2: // f: case 3: // fp: case 4: // rt: case 5: // p: tok = tokens.next(); if (!tok.equals(""-"")) { pat = tok; } try { fmt = new DecimalFormat(pat, new DecimalFormatSymbols(loc)); fmt.setParseStrict(strict); } catch (IllegalArgumentException iae) { errln(where + ""Pattern \"""" + pat + '""'); iae.printStackTrace(); tokens.next(); // consume remaining tokens //tokens.next(); if (cmd == 3) tokens.next(); continue; } str = null; try { if (cmd == 2 || cmd == 3 || cmd == 4) { // f: // fp: // rt: String num = tokens.next(); str = tokens.next(); Number n = ref.parse(num); assertEquals(where + '""' + pat + ""\"".format("" + num + "")"", str, fmt.format(n)); if (cmd == 3) { // fp: n = ref.parse(tokens.next()); } if (cmd != 2) { // != f: assertEquals(where + '""' + pat + ""\"".parse(\"""" + str + ""\"")"", n, fmt.parse(str)); } } // p: else { str = tokens.next(); String expstr = tokens.next(); Number parsed = fmt.parse(str); Number exp = ref.parse(expstr); assertEquals(where + '""' + pat + ""\"".parse(\"""" + str + ""\"")"", exp, parsed); } } catch (ParseException e) { errln(where + '""' + pat + ""\"".parse(\"""" + str + ""\"") threw an exception""); e.printStackTrace(); } break; case 6: // perr: errln(""Under construction""); return; case 7: // pat: String testpat = tokens.next(); String exppat = tokens.next(); boolean err = exppat.equals(""err""); if (testpat.equals(""-"")) { if (err) { errln(""Invalid command \""pat: - err\"" at "" + tokens.describePosition()); continue; } testpat = pat; } if (exppat.equals(""-"")) exppat = testpat; try { DecimalFormat f = null; if (testpat == pat) { // [sic] f = fmt; } else { f = new DecimalFormat(testpat); f.setParseStrict(strict); } if (err) { errln(where + ""Invalid pattern \"""" + testpat + ""\"" was accepted""); } else { assertEquals(where + '""' + testpat + ""\"".toPattern()"", exppat, f.toPattern()); } } catch (IllegalArgumentException iae2) { if (err) { logln(""Ok: "" + where + ""Invalid pattern \"""" + testpat + ""\"" threw an exception""); } else { errln(where + ""Valid pattern \"""" + testpat + ""\"" threw an exception""); iae2.printStackTrace(); } } break; case 8: // fpc: tok = tokens.next(); if (!tok.equals(""-"")) { mloc = tok; ULocale l = new ULocale(mloc); try { mfmt = MeasureFormat.getCurrencyFormat(l); } catch (IllegalArgumentException iae) { errln(where + ""Loc \"""" + tok + '""'); iae.printStackTrace(); tokens.next(); // consume remaining tokens tokens.next(); tokens.next(); continue; } } str = null; try { // fpc: String currAmt = tokens.next(); str = tokens.next(); CurrencyAmount target = parseCurrencyAmount(currAmt, ref, '/'); String formatResult = mfmt.format(target); assertEquals(where + ""getCurrencyFormat("" + mloc + "").format("" + currAmt + "")"", str, formatResult); target = parseCurrencyAmount(tokens.next(), ref, '/'); CurrencyAmount parseResult = (CurrencyAmount) mfmt.parseObject(str); assertEquals(where + ""getCurrencyFormat("" + mloc + "").parse(\"""" + str + ""\"")"", target, parseResult); } catch (ParseException e) { errln(where + '""' + pat + ""\"".parse(\"""" + str + ""\"") threw an exception""); e.printStackTrace(); } break; case 9: // strict= true or false strict = ""true"".equalsIgnoreCase(tokens.next()); logln(""Setting strict to:\t"" + strict); break; case -1: errln(""Unknown command \"""" + tok + ""\"" at "" + tokens.describePosition()); return; } } } catch (java.io.IOException e) { throw new RuntimeException(e); } finally { try { reader.close(); } catch (IOException ignored) { } } } @Test public void TestFieldPositionDecimal() { DecimalFormat nf = (DecimalFormat) android.icu.text.NumberFormat.getInstance(ULocale.ENGLISH); nf.setPositivePrefix(""FOO""); nf.setPositiveSuffix(""BA""); StringBuffer buffer = new StringBuffer(); FieldPosition fp = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR); nf.format(35.47, buffer, fp); assertEquals(""35.47"", ""FOO35.47BA"", buffer.toString()); assertEquals(""fp begin"", 5, fp.getBeginIndex()); assertEquals(""fp end"", 6, fp.getEndIndex()); } @Test public void TestFieldPositionInteger() { DecimalFormat nf = (DecimalFormat) android.icu.text.NumberFormat.getInstance(ULocale.ENGLISH); nf.setPositivePrefix(""FOO""); nf.setPositiveSuffix(""BA""); StringBuffer buffer = new StringBuffer(); FieldPosition fp = new FieldPosition(NumberFormat.Field.INTEGER); nf.format(35.47, buffer, fp); assertEquals(""35.47"", ""FOO35.47BA"", buffer.toString()); assertEquals(""fp begin"", 3, fp.getBeginIndex()); assertEquals(""fp end"", 5, fp.getEndIndex()); } @Test public void TestFieldPositionFractionButInteger() { DecimalFormat nf = (DecimalFormat) android.icu.text.NumberFormat.getInstance(ULocale.ENGLISH); nf.setPositivePrefix(""FOO""); nf.setPositiveSuffix(""BA""); StringBuffer buffer = new StringBuffer(); FieldPosition fp = new FieldPosition(NumberFormat.Field.FRACTION); nf.format(35, buffer, fp); assertEquals(""35"", ""FOO35BA"", buffer.toString()); assertEquals(""fp begin"", 5, fp.getBeginIndex()); assertEquals(""fp end"", 5, fp.getEndIndex()); } @Test public void TestFieldPositionFraction() { DecimalFormat nf = (DecimalFormat) android.icu.text.NumberFormat.getInstance(ULocale.ENGLISH); nf.setPositivePrefix(""FOO""); nf.setPositiveSuffix(""BA""); StringBuffer buffer = new StringBuffer(); FieldPosition fp = new FieldPosition(NumberFormat.Field.FRACTION); nf.format(35.47, buffer, fp); assertEquals(""35.47"", ""FOO35.47BA"", buffer.toString()); assertEquals(""fp begin"", 6, fp.getBeginIndex()); assertEquals(""fp end"", 8, fp.getEndIndex()); } @Test public void TestFieldPositionCurrency() { DecimalFormat nf = (DecimalFormat) android.icu.text.NumberFormat.getCurrencyInstance(Locale.US); double amount = 35.47; double negAmount = -34.567; FieldPosition cp = new FieldPosition(NumberFormat.Field.CURRENCY); StringBuffer buffer0 = new StringBuffer(); nf.format(amount, buffer0, cp); assertEquals(""$35.47"", ""$35.47"", buffer0.toString()); assertEquals(""cp begin"", 0, cp.getBeginIndex()); assertEquals(""cp end"", 1, cp.getEndIndex()); StringBuffer buffer01 = new StringBuffer(); nf.format(negAmount, buffer01, cp); assertEquals(""-$34.57"", ""-$34.57"", buffer01.toString()); assertEquals(""cp begin"", 1, cp.getBeginIndex()); assertEquals(""cp end"", 2, cp.getEndIndex()); nf.setCurrency(Currency.getInstance(Locale.FRANCE)); StringBuffer buffer1 = new StringBuffer(); nf.format(amount, buffer1, cp); assertEquals(""€35.47"", ""€35.47"", buffer1.toString()); assertEquals(""cp begin"", 0, cp.getBeginIndex()); assertEquals(""cp end"", 1, cp.getEndIndex()); nf.setCurrency(Currency.getInstance(new Locale(""fr"", ""ch"", """"))); StringBuffer buffer2 = new StringBuffer(); nf.format(amount, buffer2, cp); assertEquals(""CHF35.47"", ""CHF35.47"", buffer2.toString()); assertEquals(""cp begin"", 0, cp.getBeginIndex()); assertEquals(""cp end"", 3, cp.getEndIndex()); StringBuffer buffer20 = new StringBuffer(); nf.format(negAmount, buffer20, cp); assertEquals(""-CHF34.57"", ""-CHF34.57"", buffer20.toString()); assertEquals(""cp begin"", 1, cp.getBeginIndex()); assertEquals(""cp end"", 4, cp.getEndIndex()); nf = (DecimalFormat) android.icu.text.NumberFormat.getCurrencyInstance(Locale.FRANCE); StringBuffer buffer3 = new StringBuffer(); nf.format(amount, buffer3, cp); assertEquals(""35,47 €"", ""35,47 €"", buffer3.toString()); assertEquals(""cp begin"", 6, cp.getBeginIndex()); assertEquals(""cp end"", 7, cp.getEndIndex()); StringBuffer buffer4 = new StringBuffer(); nf.format(negAmount, buffer4, cp); assertEquals(""-34,57 €"", ""-34,57 €"", buffer4.toString()); assertEquals(""cp begin"", 7, cp.getBeginIndex()); assertEquals(""cp end"", 8, cp.getEndIndex()); nf.setCurrency(Currency.getInstance(new Locale(""fr"", ""ch""))); StringBuffer buffer5 = new StringBuffer(); nf.format(negAmount, buffer5, cp); assertEquals(""-34,57 CHF"", ""-34,57 CHF"", buffer5.toString()); assertEquals(""cp begin"", 7, cp.getBeginIndex()); assertEquals(""cp end"", 10, cp.getEndIndex()); NumberFormat plCurrencyFmt = NumberFormat.getInstance(new Locale(""fr"", ""ch""), NumberFormat.PLURALCURRENCYSTYLE); StringBuffer buffer6 = new StringBuffer(); plCurrencyFmt.format(negAmount, buffer6, cp); assertEquals(""-34.57 francs suisses"", ""-34.57 francs suisses"", buffer6.toString()); assertEquals(""cp begin"", 7, cp.getBeginIndex()); assertEquals(""cp end"", 21, cp.getEndIndex()); // Positive value with PLURALCURRENCYSTYLE. plCurrencyFmt = NumberFormat.getInstance(new Locale(""ja"", ""ch""), NumberFormat.PLURALCURRENCYSTYLE); StringBuffer buffer7 = new StringBuffer(); plCurrencyFmt.format(amount, buffer7, cp); assertEquals(""35.47スイス フラン"", ""35.47スイス フラン"", buffer7.toString()); assertEquals(""cp begin"", 5, cp.getBeginIndex()); assertEquals(""cp end"", 12, cp.getEndIndex()); // PLURALCURRENCYSTYLE for non-ASCII. plCurrencyFmt = NumberFormat.getInstance(new Locale(""ja"", ""de""), NumberFormat.PLURALCURRENCYSTYLE); StringBuffer buffer8 = new StringBuffer(); plCurrencyFmt.format(negAmount, buffer8, cp); assertEquals(""-34.57ユーロ"", ""-34.57ユーロ"", buffer8.toString()); assertEquals(""cp begin"", 6, cp.getBeginIndex()); assertEquals(""cp end"", 9, cp.getEndIndex()); nf = (DecimalFormat) android.icu.text.NumberFormat.getCurrencyInstance(Locale.JAPAN); nf.setCurrency(Currency.getInstance(new Locale(""ja"", ""jp""))); StringBuffer buffer9 = new StringBuffer(); nf.format(negAmount, buffer9, cp); assertEquals(""-¥35"", ""-¥35"", buffer9.toString()); assertEquals(""cp begin"", 1, cp.getBeginIndex()); assertEquals(""cp end"", 2, cp.getEndIndex()); // Negative value with PLURALCURRENCYSTYLE. plCurrencyFmt = NumberFormat.getInstance(new Locale(""ja"", ""ch""), NumberFormat.PLURALCURRENCYSTYLE); StringBuffer buffer10 = new StringBuffer(); plCurrencyFmt.format(negAmount, buffer10, cp); assertEquals(""-34.57スイス フラン"", ""-34.57スイス フラン"", buffer10.toString()); assertEquals(""cp begin"", 6, cp.getBeginIndex()); assertEquals(""cp end"", 13, cp.getEndIndex()); // Nagative value with PLURALCURRENCYSTYLE, Arabic digits. nf = (DecimalFormat) android.icu.text.NumberFormat.getCurrencyInstance(new Locale(""ar"", ""eg"")); plCurrencyFmt = NumberFormat.getInstance(new Locale(""ar"", ""eg""), NumberFormat.PLURALCURRENCYSTYLE); StringBuffer buffer11 = new StringBuffer(); plCurrencyFmt.format(negAmount, buffer11, cp); assertEquals(""؜-٣٤٫٥٧ جنيه مصري"", ""؜-٣٤٫٥٧ جنيه مصري"", buffer11.toString()); assertEquals(""cp begin"", 8, cp.getBeginIndex()); assertEquals(""cp end"", 17, cp.getEndIndex()); } @Test public void TestRounding() { DecimalFormat nf = (DecimalFormat) android.icu.text.NumberFormat.getInstance(ULocale.ENGLISH); if (false) { // for debugging specific value nf.setRoundingMode(BigDecimal.ROUND_HALF_UP); checkRounding(nf, new BigDecimal(""300.0300000000""), 0, new BigDecimal(""0.020000000"")); } // full tests int[] roundingIncrements = {1, 2, 5, 20, 50, 100}; int[] testValues = {0, 300}; for (int j = 0; j < testValues.length; ++j) { for (int mode = BigDecimal.ROUND_UP; mode < BigDecimal.ROUND_HALF_EVEN; ++mode) { nf.setRoundingMode(mode); for (int increment = 0; increment < roundingIncrements.length; ++increment) { BigDecimal base = new BigDecimal(testValues[j]); BigDecimal rInc = new BigDecimal(roundingIncrements[increment]); checkRounding(nf, base, 20, rInc); rInc = new BigDecimal(""1.000000000"").divide(rInc); checkRounding(nf, base, 20, rInc); } } } } @Test public void TestRoundingPattern() { class TestRoundingPatternItem { String pattern; double roundingIncrement; double testCase; String expected; TestRoundingPatternItem(String pattern, double roundingIncrement, double testCase, String expected) { this.pattern = pattern; this.roundingIncrement = roundingIncrement; this.testCase = testCase; this.expected = expected; } }; TestRoundingPatternItem []tests = { new TestRoundingPatternItem(""##0.65"", 0.65, 1.234, ""1.30""), new TestRoundingPatternItem(""#50"", 50.0, 1230, ""1250"") }; DecimalFormat df = (DecimalFormat) android.icu.text.NumberFormat.getInstance(ULocale.ENGLISH); String result; BigDecimal bd; for (int i = 0; i < tests.length; i++) { df.applyPattern(tests[i].pattern); result = df.format(tests[i].testCase); if (!tests[i].expected.equals(result)) { errln(""String Pattern Rounding Test Failed: Pattern: \"""" + tests[i].pattern + ""\"" Number: "" + tests[i].testCase + "" - Got: "" + result + "" Expected: "" + tests[i].expected); } bd = new BigDecimal(tests[i].roundingIncrement); df.setRoundingIncrement(bd); result = df.format(tests[i].testCase); if (!tests[i].expected.equals(result)) { errln(""BigDecimal Rounding Test Failed: Pattern: \"""" + tests[i].pattern + ""\"" Number: "" + tests[i].testCase + "" - Got: "" + result + "" Expected: "" + tests[i].expected); } } } @Test public void TestBigDecimalRounding() { String figure = ""50.000000004""; Double dbl = new Double(figure); BigDecimal dec = new BigDecimal(figure); DecimalFormat f = (DecimalFormat) NumberFormat.getInstance(); f.applyPattern(""00.00######""); assertEquals(""double format"", ""50.00"", f.format(dbl)); assertEquals(""bigdec format"", ""50.00"", f.format(dec)); int maxFracDigits = f.getMaximumFractionDigits(); BigDecimal roundingIncrement = new BigDecimal(""1"").movePointLeft(maxFracDigits); f.setRoundingIncrement(roundingIncrement); f.setRoundingMode(BigDecimal.ROUND_DOWN); assertEquals(""Rounding down"", f.format(dbl), f.format(dec)); f.setRoundingIncrement(roundingIncrement); f.setRoundingMode(BigDecimal.ROUND_HALF_UP); assertEquals(""Rounding half up"", f.format(dbl), f.format(dec)); } void checkRounding(DecimalFormat nf, BigDecimal base, int iterations, BigDecimal increment) { nf.setRoundingIncrement(increment.toBigDecimal()); BigDecimal lastParsed = new BigDecimal(Integer.MIN_VALUE); // used to make sure that rounding is monotonic for (int i = -iterations; i <= iterations; ++i) { BigDecimal iValue = base.add(increment.multiply(new BigDecimal(i)).movePointLeft(1)); BigDecimal smallIncrement = new BigDecimal(""0.00000001""); if (iValue.signum() != 0) { smallIncrement = smallIncrement.multiply(iValue); // scale unless zero } // we not only test the value, but some values in a small range around it. lastParsed = checkRound(nf, iValue.subtract(smallIncrement), lastParsed); lastParsed = checkRound(nf, iValue, lastParsed); lastParsed = checkRound(nf, iValue.add(smallIncrement), lastParsed); } } private BigDecimal checkRound(DecimalFormat nf, BigDecimal iValue, BigDecimal lastParsed) { String formatedBigDecimal = nf.format(iValue); String formattedDouble = nf.format(iValue.doubleValue()); if (!equalButForTrailingZeros(formatedBigDecimal, formattedDouble)) { errln(""Failure at: "" + iValue + "" ("" + iValue.doubleValue() + "")"" + "",\tRounding-mode: "" + roundingModeNames[nf.getRoundingMode()] + "",\tRounding-increment: "" + nf.getRoundingIncrement() + "",\tdouble: "" + formattedDouble + "",\tBigDecimal: "" + formatedBigDecimal); } else { logln(""Value: "" + iValue + "",\tRounding-mode: "" + roundingModeNames[nf.getRoundingMode()] + "",\tRounding-increment: "" + nf.getRoundingIncrement() + "",\tdouble: "" + formattedDouble + "",\tBigDecimal: "" + formatedBigDecimal); } try { // Number should have compareTo(...) BigDecimal parsed = toBigDecimal(nf.parse(formatedBigDecimal)); if (lastParsed.compareTo(parsed) > 0) { errln(""Rounding wrong direction!: "" + lastParsed + "" > "" + parsed); } lastParsed = parsed; } catch (ParseException e) { errln(""Parse Failure with: "" + formatedBigDecimal); } return lastParsed; } static BigDecimal toBigDecimal(Number number) { return number instanceof BigDecimal ? (BigDecimal) number : number instanceof BigInteger ? new BigDecimal((BigInteger)number) : number instanceof java.math.BigDecimal ? new BigDecimal((java.math.BigDecimal)number) : number instanceof Double ? new BigDecimal(number.doubleValue()) : number instanceof Float ? new BigDecimal(number.floatValue()) : new BigDecimal(number.longValue()); } static String[] roundingModeNames = { ""ROUND_UP"", ""ROUND_DOWN"", ""ROUND_CEILING"", ""ROUND_FLOOR"", ""ROUND_HALF_UP"", ""ROUND_HALF_DOWN"", ""ROUND_HALF_EVEN"", ""ROUND_UNNECESSARY"" }; private static boolean equalButForTrailingZeros(String formatted1, String formatted2) { if (formatted1.length() == formatted2.length()) return formatted1.equals(formatted2); return stripFinalZeros(formatted1).equals(stripFinalZeros(formatted2)); } private static String stripFinalZeros(String formatted) { int len1 = formatted.length(); char ch; while (len1 > 0 && ((ch = formatted.charAt(len1-1)) == '0' || ch == '.')) --len1; if (len1==1 && ((ch = formatted.charAt(len1-1)) == '-')) --len1; return formatted.substring(0,len1); } //------------------------------------------------------------------ // Support methods //------------------------------------------------------------------ // Format-Parse test public void expect2(NumberFormat fmt, Number n, String exp) { // Don't round-trip format test, since we explicitly do it expect(fmt, n, exp, false); expect(fmt, exp, n); } // Format-Parse test public void expect3(NumberFormat fmt, Number n, String exp) { // Don't round-trip format test, since we explicitly do it expect_rbnf(fmt, n, exp, false); expect_rbnf(fmt, exp, n); } // Format-Parse test (convenience) public void expect2(NumberFormat fmt, double n, String exp) { expect2(fmt, new Double(n), exp); } // Format-Parse test (convenience) public void expect3(NumberFormat fmt, double n, String exp) { expect3(fmt, new Double(n), exp); } // Format-Parse test (convenience) public void expect2(NumberFormat fmt, long n, String exp) { expect2(fmt, new Long(n), exp); } // Format-Parse test (convenience) public void expect3(NumberFormat fmt, long n, String exp) { expect3(fmt, new Long(n), exp); } // Format test public void expect(NumberFormat fmt, Number n, String exp, boolean rt) { StringBuffer saw = new StringBuffer(); FieldPosition pos = new FieldPosition(0); fmt.format(n, saw, pos); String pat = ((DecimalFormat)fmt).toPattern(); if (saw.toString().equals(exp)) { logln(""Ok "" + n + "" x "" + pat + "" = \"""" + saw + ""\""""); // We should be able to round-trip the formatted string => // number => string (but not the other way around: number // => string => number2, might have number2 != number): if (rt) { try { Number n2 = fmt.parse(exp); StringBuffer saw2 = new StringBuffer(); fmt.format(n2, saw2, pos); if (!saw2.toString().equals(exp)) { errln(""expect() format test rt, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", FAIL \"""" + exp + ""\"" => "" + n2 + "" => \"""" + saw2 + '""'); } } catch (ParseException e) { errln(""expect() format test rt, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", "" + e.getMessage()); return; } } } else { errln(""expect() format test, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", FAIL "" + n + "" x "" + pat + "" = \"""" + saw + ""\"", expected \"""" + exp + ""\""""); } } // Format test public void expect_rbnf(NumberFormat fmt, Number n, String exp, boolean rt) { StringBuffer saw = new StringBuffer(); FieldPosition pos = new FieldPosition(0); fmt.format(n, saw, pos); if (saw.toString().equals(exp)) { logln(""Ok "" + n + "" = \"""" + saw + ""\""""); // We should be able to round-trip the formatted string => // number => string (but not the other way around: number // => string => number2, might have number2 != number): if (rt) { try { Number n2 = fmt.parse(exp); StringBuffer saw2 = new StringBuffer(); fmt.format(n2, saw2, pos); if (!saw2.toString().equals(exp)) { errln(""expect_rbnf() format test rt, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", FAIL \"""" + exp + ""\"" => "" + n2 + "" => \"""" + saw2 + '""'); } } catch (ParseException e) { errln(""expect_rbnf() format test rt, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", "" + e.getMessage()); return; } } } else { errln(""expect_rbnf() format test, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", FAIL "" + n + "" = \"""" + saw + ""\"", expected \"""" + exp + ""\""""); } } // Format test (convenience) public void expect(NumberFormat fmt, Number n, String exp) { expect(fmt, n, exp, true); } // Format test (convenience) public void expect(NumberFormat fmt, double n, String exp) { expect(fmt, new Double(n), exp); } // Format test (convenience) public void expect(NumberFormat fmt, long n, String exp) { expect(fmt, new Long(n), exp); } // Parse test public void expect(NumberFormat fmt, String str, Number n) { Number num = null; try { num = fmt.parse(str); } catch (ParseException e) { errln(e.getMessage()); return; } String pat = ((DecimalFormat)fmt).toPattern(); // A little tricky here -- make sure Double(12345.0) and // Long(12345) match. if (num.equals(n) || num.doubleValue() == n.doubleValue()) { logln(""Ok \"""" + str + ""\"" x "" + pat + "" = "" + num); } else { errln(""expect() parse test, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", FAIL \"""" + str + ""\"" x "" + pat + "" = "" + num + "", expected "" + n); } } // Parse test public void expect_rbnf(NumberFormat fmt, String str, Number n) { Number num = null; try { num = fmt.parse(str); } catch (ParseException e) { errln(e.getMessage()); return; } // A little tricky here -- make sure Double(12345.0) and // Long(12345) match. if (num.equals(n) || num.doubleValue() == n.doubleValue()) { logln(""Ok \"""" + str + "" = "" + num); } else { errln(""expect_rbnf() parse test, locale "" + fmt.getLocale(ULocale.VALID_LOCALE) + "", FAIL \"""" + str + "" = "" + num + "", expected "" + n); } } // Parse test (convenience) public void expect(NumberFormat fmt, String str, double n) { expect(fmt, str, new Double(n)); } // Parse test (convenience) public void expect(NumberFormat fmt, String str, long n) { expect(fmt, str, new Long(n)); } private void expectCurrency(NumberFormat nf, Currency curr, double value, String string) { DecimalFormat fmt = (DecimalFormat) nf; if (curr != null) { fmt.setCurrency(curr); } String s = fmt.format(value).replace('\u00A0', ' '); if (s.equals(string)) { logln(""Ok: "" + value + "" x "" + curr + "" => "" + s); } else { errln(""FAIL: "" + value + "" x "" + curr + "" => "" + s + "", expected "" + string); } } public void expectPad(DecimalFormat fmt, String pat, int pos) { expectPad(fmt, pat, pos, 0, (char)0); } public void expectPad(DecimalFormat fmt, final String pat, int pos, int width, final char pad) { int apos = 0, awidth = 0; char apadStr; try { fmt.applyPattern(pat); apos = fmt.getPadPosition(); awidth = fmt.getFormatWidth(); apadStr = fmt.getPadCharacter(); } catch (Exception e) { apos = -1; awidth = width; apadStr = pad; } if (apos == pos && awidth == width && apadStr == pad) { logln(""Ok \"""" + pat + ""\"" pos="" + apos + ((pos == -1) ? """" : "" width="" + awidth + "" pad="" + apadStr)); } else { errln(""FAIL \"""" + pat + ""\"" pos="" + apos + "" width="" + awidth + "" pad="" + apadStr + "", expected "" + pos + "" "" + width + "" "" + pad); } } public void expectPat(DecimalFormat fmt, final String exp) { String pat = fmt.toPattern(); if (pat.equals(exp)) { logln(""Ok \"""" + pat + ""\""""); } else { errln(""FAIL \"""" + pat + ""\"", expected \"""" + exp + ""\""""); } } private void expectParseCurrency(NumberFormat fmt, Currency expected, String text) { ParsePosition pos = new ParsePosition(0); CurrencyAmount currencyAmount = fmt.parseCurrency(text, pos); assertTrue(""Parse of "" + text + "" should have succeeded."", pos.getIndex() > 0); assertEquals(""Currency should be correct."", expected, currencyAmount.getCurrency()); } @Test public void TestJB3832(){ ULocale locale = new ULocale(""pt_PT@currency=PTE""); NumberFormat format = NumberFormat.getCurrencyInstance(locale); Currency curr = Currency.getInstance(locale); logln(""\nName of the currency is: "" + curr.getName(locale, Currency.LONG_NAME, new boolean[] {false})); CurrencyAmount cAmt = new CurrencyAmount(1150.50, curr); logln(""CurrencyAmount object's hashCode is: "" + cAmt.hashCode()); //cover hashCode String str = format.format(cAmt); String expected = ""1,150$50\u00a0\u200b""; if(!expected.equals(str)){ errln(""Did not get the expected output Expected: ""+expected+"" Got: ""+ str); } } @Test public void TestStrictParse() { String[] pass = { ""0"", // single zero before end of text is not leading ""0 "", // single zero at end of number is not leading ""0."", // single zero before period (or decimal, it's ambiguous) is not leading ""0,"", // single zero before comma (not group separator) is not leading ""0.0"", // single zero before decimal followed by digit is not leading ""0. "", // same as above before period (or decimal) is not leading ""0.100,5"", // comma stops parse of decimal (no grouping) "".00"", // leading decimal is ok, even with zeros ""1234567"", // group separators are not required ""12345, "", // comma not followed by digit is not a group separator, but end of number ""1,234, "", // if group separator is present, group sizes must be appropriate ""1,234,567"", // ...secondary too ""0E"", // an exponent not followed by zero or digits is not an exponent ""00"", // leading zero before zero - used to be error - see ticket #7913 ""012"", // leading zero before digit - used to be error - see ticket #7913 ""0,456"", // leading zero before group separator - used to be error - see ticket #7913 }; String[] fail = { ""1,2"", // wrong number of digits after group separator "",0"", // leading group separator before zero "",1"", // leading group separator before digit "",.02"", // leading group separator before decimal ""1,.02"", // group separator before decimal ""1,,200"", // multiple group separators ""1,45"", // wrong number of digits in primary group ""1,45 that"", // wrong number of digits in primary group ""1,45.34"", // wrong number of digits in primary group ""1234,567"", // wrong number of digits in secondary group ""12,34,567"", // wrong number of digits in secondary group ""1,23,456,7890"", // wrong number of digits in primary and secondary groups }; DecimalFormat nf = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH); runStrictParseBatch(nf, pass, fail); String[] scientificPass = { ""0E2"", // single zero before exponent is ok ""1234E2"", // any number of digits before exponent is ok ""1,234E"", // an exponent string not followed by zero or digits is not an exponent ""00E2"", // leading zeroes now allowed in strict mode - see ticket # }; String[] scientificFail = { ""1,234E2"", // group separators with exponent fail }; nf = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH); runStrictParseBatch(nf, scientificPass, scientificFail); String[] mixedPass = { ""12,34,567"", ""12,34,567,"", ""12,34,567, that"", ""12,34,567 that"", }; String[] mixedFail = { ""12,34,56"", ""12,34,56,"", ""12,34,56, that "", ""12,34,56 that"", }; nf = new DecimalFormat(""#,##,##0.#""); runStrictParseBatch(nf, mixedPass, mixedFail); } void runStrictParseBatch(DecimalFormat nf, String[] pass, String[] fail) { nf.setParseStrict(false); runStrictParseTests(""should pass"", nf, pass, true); runStrictParseTests(""should also pass"", nf, fail, true); nf.setParseStrict(true); runStrictParseTests(""should still pass"", nf, pass, true); runStrictParseTests(""should fail"", nf, fail, false); } void runStrictParseTests(String msg, DecimalFormat nf, String[] tests, boolean pass) { logln(""""); logln(""pattern: '"" + nf.toPattern() + ""'""); logln(msg); for (int i = 0; i < tests.length; ++i) { String str = tests[i]; ParsePosition pp = new ParsePosition(0); Number n = nf.parse(str, pp); String formatted = n != null ? nf.format(n) : ""null""; String err = pp.getErrorIndex() == -1 ? """" : ""(error at "" + pp.getErrorIndex() + "")""; if ((err.length() == 0) != pass) { errln(""'"" + str + ""' parsed '"" + str.substring(0, pp.getIndex()) + ""' returned "" + n + "" formats to '"" + formatted + ""' "" + err); } else { if (err.length() > 0) { err = ""got expected "" + err; } logln(""'"" + str + ""' parsed '"" + str.substring(0, pp.getIndex()) + ""' returned "" + n + "" formats to '"" + formatted + ""' "" + err); } } } @Test public void TestJB5251(){ //save default locale ULocale defaultLocale = ULocale.getDefault(); ULocale.setDefault(new ULocale(""qr_QR"")); try { NumberFormat.getInstance(); } catch (Exception e) { errln(""Numberformat threw exception for non-existent locale. It should use the default.""); } //reset default locale ULocale.setDefault(defaultLocale); } @Test public void TestParseReturnType() { String[] defaultNonBigDecimals = { ""123"", // Long ""123.0"", // Long ""0.0"", // Long ""12345678901234567890"" // BigInteger }; String[] doubles = { ""-0.0"", ""NaN"", ""\u221E"" // Infinity }; DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US); DecimalFormat nf = new DecimalFormat(""#.#"", sym); if (nf.isParseBigDecimal()) { errln(""FAIL: isParseDecimal() must return false by default""); } // isParseBigDecimal() is false for (int i = 0; i < defaultNonBigDecimals.length; i++) { try { Number n = nf.parse(defaultNonBigDecimals[i]); if (n instanceof BigDecimal) { errln(""FAIL: parse returns BigDecimal instance""); } } catch (ParseException e) { errln(""parse of '"" + defaultNonBigDecimals[i] + ""' threw exception: "" + e); } } // parse results for doubls must be always Double for (int i = 0; i < doubles.length; i++) { try { Number n = nf.parse(doubles[i]); if (!(n instanceof Double)) { errln(""FAIL: parse does not return Double instance""); } } catch (ParseException e) { errln(""parse of '"" + doubles[i] + ""' threw exception: "" + e); } } // force this DecimalFormat to return BigDecimal nf.setParseBigDecimal(true); if (!nf.isParseBigDecimal()) { errln(""FAIL: isParseBigDecimal() must return true""); } // isParseBigDecimal() is true for (int i = 0; i < defaultNonBigDecimals.length; i++) { try { Number n = nf.parse(defaultNonBigDecimals[i]); if (!(n instanceof BigDecimal)) { errln(""FAIL: parse does not return BigDecimal instance""); } } catch (ParseException e) { errln(""parse of '"" + defaultNonBigDecimals[i] + ""' threw exception: "" + e); } } // parse results for doubls must be always Double for (int i = 0; i < doubles.length; i++) { try { Number n = nf.parse(doubles[i]); if (!(n instanceof Double)) { errln(""FAIL: parse does not return Double instance""); } } catch (ParseException e) { errln(""parse of '"" + doubles[i] + ""' threw exception: "" + e); } } } @Test public void TestNonpositiveMultiplier() { DecimalFormat df = new DecimalFormat(""0""); // test zero multiplier try { df.setMultiplier(0); // bad errln(""DecimalFormat.setMultiplier(0) did not throw an IllegalArgumentException""); } catch (IllegalArgumentException ex) { // good } // test negative multiplier try { df.setMultiplier(-1); if (df.getMultiplier() != -1) { errln(""DecimalFormat.setMultiplier(-1) did not change the multiplier to -1""); return; } // good } catch (IllegalArgumentException ex) { // bad errln(""DecimalFormat.setMultiplier(-1) threw an IllegalArgumentException""); return; } expect(df, ""1122.123"", -1122.123); expect(df, ""-1122.123"", 1122.123); expect(df, ""1.2"", -1.2); expect(df, ""-1.2"", 1.2); expect2(df, Long.MAX_VALUE, BigInteger.valueOf(Long.MAX_VALUE).negate().toString()); expect2(df, Long.MIN_VALUE, BigInteger.valueOf(Long.MIN_VALUE).negate().toString()); expect2(df, Long.MAX_VALUE / 2, BigInteger.valueOf(Long.MAX_VALUE / 2).negate().toString()); expect2(df, Long.MIN_VALUE / 2, BigInteger.valueOf(Long.MIN_VALUE / 2).negate().toString()); expect2(df, BigDecimal.valueOf(Long.MAX_VALUE), BigDecimal.valueOf(Long.MAX_VALUE).negate().toString()); expect2(df, BigDecimal.valueOf(Long.MIN_VALUE), BigDecimal.valueOf(Long.MIN_VALUE).negate().toString()); expect2(df, java.math.BigDecimal.valueOf(Long.MAX_VALUE), java.math.BigDecimal.valueOf(Long.MAX_VALUE).negate().toString()); expect2(df, java.math.BigDecimal.valueOf(Long.MIN_VALUE), java.math.BigDecimal.valueOf(Long.MIN_VALUE).negate().toString()); } @Test public void TestJB5358() { int numThreads = 10; String numstr = ""12345""; double expected = 12345; DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US); DecimalFormat fmt = new DecimalFormat(""#.#"", sym); ArrayList errors = new ArrayList(); ParseThreadJB5358[] threads = new ParseThreadJB5358[numThreads]; for (int i = 0; i < numThreads; i++) { threads[i] = new ParseThreadJB5358((DecimalFormat)fmt.clone(), numstr, expected, errors); threads[i].start(); } for (int i = 0; i < numThreads; i++) { try { threads[i].join(); } catch (InterruptedException ie) { ie.printStackTrace(); } } if (errors.size() != 0) { StringBuffer errBuf = new StringBuffer(); for (int i = 0; i < errors.size(); i++) { errBuf.append((String)errors.get(i)); errBuf.append(""\n""); } errln(""FAIL: "" + errBuf); } } static private class ParseThreadJB5358 extends Thread { private final DecimalFormat decfmt; private final String numstr; private final double expect; private final ArrayList errors; public ParseThreadJB5358(DecimalFormat decfmt, String numstr, double expect, ArrayList errors) { this.decfmt = decfmt; this.numstr = numstr; this.expect = expect; this.errors = errors; } @Override public void run() { for (int i = 0; i < 10000; i++) { try { Number n = decfmt.parse(numstr); if (n.doubleValue() != expect) { synchronized(errors) { errors.add(new String(""Bad parse result - expected:"" + expect + "" actual:"" + n.doubleValue())); } } } catch (Throwable t) { synchronized(errors) { errors.add(new String(t.getClass().getName() + "" - "" + t.getMessage())); } } } } } @Test public void TestSetCurrency() { DecimalFormatSymbols decf1 = DecimalFormatSymbols.getInstance(ULocale.US); DecimalFormatSymbols decf2 = DecimalFormatSymbols.getInstance(ULocale.US); decf2.setCurrencySymbol(""UKD""); DecimalFormat format1 = new DecimalFormat(""000.000"", decf1); DecimalFormat format2 = new DecimalFormat(""000.000"", decf2); Currency euro = Currency.getInstance(""EUR""); format1.setCurrency(euro); format2.setCurrency(euro); assertEquals(""Reset with currency symbol"", format1, format2); } /* * Testing the method public StringBuffer format(Object number, ...) */ @Test public void TestFormat() { NumberFormat nf = NumberFormat.getInstance(); StringBuffer sb = new StringBuffer(""dummy""); FieldPosition fp = new FieldPosition(0); // Tests when ""if (number instanceof Long)"" is true try { nf.format(new Long(""0""), sb, fp); } catch (Exception e) { errln(""NumberFormat.format(Object number, ...) was not suppose to "" + ""return an exception for a Long object. Error: "" + e); } // Tests when ""else if (number instanceof BigInteger)"" is true try { nf.format((Object)new BigInteger(""0""), sb, fp); } catch (Exception e) { errln(""NumberFormat.format(Object number, ...) was not suppose to "" + ""return an exception for a BigInteger object. Error: "" + e); } // Tests when ""else if (number instanceof java.math.BigDecimal)"" is true try { nf.format((Object)new java.math.BigDecimal(""0""), sb, fp); } catch (Exception e) { errln(""NumberFormat.format(Object number, ...) was not suppose to "" + ""return an exception for a java.math.BigDecimal object. Error: "" + e); } // Tests when ""else if (number instanceof android.icu.math.BigDecimal)"" is true try { nf.format((Object)new android.icu.math.BigDecimal(""0""), sb, fp); } catch (Exception e) { errln(""NumberFormat.format(Object number, ...) was not suppose to "" + ""return an exception for a android.icu.math.BigDecimal object. Error: "" + e); } // Tests when ""else if (number instanceof CurrencyAmount)"" is true try { CurrencyAmount ca = new CurrencyAmount(0.0, Currency.getInstance(new ULocale(""en_US""))); nf.format((Object)ca, sb, fp); } catch (Exception e) { errln(""NumberFormat.format(Object number, ...) was not suppose to "" + ""return an exception for a CurrencyAmount object. Error: "" + e); } // Tests when ""else if (number instanceof Number)"" is true try { nf.format(0.0, sb, fp); } catch (Exception e) { errln(""NumberFormat.format(Object number, ...) was not suppose to "" + ""to return an exception for a Number object. Error: "" + e); } // Tests when ""else"" is true try { nf.format(new Object(), sb, fp); errln(""NumberFormat.format(Object number, ...) was suppose to "" + ""return an exception for an invalid object.""); } catch (Exception e) { } try { nf.format(new String(""dummy""), sb, fp); errln(""NumberFormat.format(Object number, ...) was suppose to "" + ""return an exception for an invalid object.""); } catch (Exception e) { } } /* * Coverage tests for the implementation of abstract format methods not being called otherwise */ public void TestFormatAbstractImplCoverage() { NumberFormat df = DecimalFormat.getInstance(Locale.ENGLISH); NumberFormat cdf = CompactDecimalFormat.getInstance(Locale.ENGLISH, CompactDecimalFormat.CompactStyle.SHORT); NumberFormat rbf = new RuleBasedNumberFormat(ULocale.ENGLISH, RuleBasedNumberFormat.SPELLOUT); /* * Test NumberFormat.format(BigDecimal,StringBuffer,FieldPosition) */ StringBuffer sb = new StringBuffer(); String result = df.format(new BigDecimal(2000.43), sb, new FieldPosition(0)).toString(); if (!""2,000.43"".equals(result)) { errln(""DecimalFormat failed. Expected: 2,000.43 - Actual: "" + result); } sb.delete(0, sb.length()); result = cdf.format(new BigDecimal(2000.43), sb, new FieldPosition(0)).toString(); if (!""2K"".equals(result)) { errln(""DecimalFormat failed. Expected: 2K - Actual: "" + result); } sb.delete(0, sb.length()); result = rbf.format(new BigDecimal(2000.43), sb, new FieldPosition(0)).toString(); if (!""two thousand point four three"".equals(result)) { errln(""DecimalFormat failed. Expected: 'two thousand point four three' - Actual: '"" + result + ""'""); } } /* * Tests the method public final static NumberFormat getInstance(int style) public static NumberFormat * getInstance(Locale inLocale, int style) public static NumberFormat getInstance(ULocale desiredLocale, int choice) */ @Test public void TestGetInstance() { // Tests ""public final static NumberFormat getInstance(int style)"" int maxStyle = NumberFormat.STANDARDCURRENCYSTYLE; int[] invalid_cases = { NumberFormat.NUMBERSTYLE - 1, NumberFormat.NUMBERSTYLE - 2, maxStyle + 1, maxStyle + 2 }; for (int i = NumberFormat.NUMBERSTYLE; i < maxStyle; i++) { try { NumberFormat.getInstance(i); } catch (Exception e) { errln(""NumberFormat.getInstance(int style) was not suppose to "" + ""return an exception for passing value of "" + i); } } for (int i = 0; i < invalid_cases.length; i++) { try { NumberFormat.getInstance(invalid_cases[i]); errln(""NumberFormat.getInstance(int style) was suppose to "" + ""return an exception for passing value of "" + invalid_cases[i]); } catch (Exception e) { } } // Tests ""public static NumberFormat getInstance(Locale inLocale, int style)"" String[] localeCases = { ""en_US"", ""fr_FR"", ""de_DE"", ""jp_JP"" }; for (int i = NumberFormat.NUMBERSTYLE; i < maxStyle; i++) { for (int j = 0; j < localeCases.length; j++) { try { NumberFormat.getInstance(new Locale(localeCases[j]), i); } catch (Exception e) { errln(""NumberFormat.getInstance(Locale inLocale, int style) was not suppose to "" + ""return an exception for passing value of "" + localeCases[j] + "", "" + i); } } } // Tests ""public static NumberFormat getInstance(ULocale desiredLocale, int choice)"" // Tests when ""if (choice < NUMBERSTYLE || choice > PLURALCURRENCYSTYLE)"" is true for (int i = 0; i < invalid_cases.length; i++) { try { NumberFormat.getInstance((ULocale) null, invalid_cases[i]); errln(""NumberFormat.getInstance(ULocale inLocale, int choice) was not suppose to "" + ""return an exception for passing value of "" + invalid_cases[i]); } catch (Exception e) { } } } /* * Tests the class public static abstract class NumberFormatFactory */ @Test public void TestNumberFormatFactory() { /* * The following class allows the method public NumberFormat createFormat(Locale loc, int formatType) to be * tested. */ class TestFactory extends NumberFormatFactory { @Override public Set getSupportedLocaleNames() { return null; } @Override public NumberFormat createFormat(ULocale loc, int formatType) { return null; } } /* * The following class allows the method public NumberFormat createFormat(ULocale loc, int formatType) to be * tested. */ class TestFactory1 extends NumberFormatFactory { @Override public Set getSupportedLocaleNames() { return null; } @Override public NumberFormat createFormat(Locale loc, int formatType) { return null; } } TestFactory tf = new TestFactory(); TestFactory1 tf1 = new TestFactory1(); /* * Tests the method public boolean visible() */ if (tf.visible() != true) { errln(""NumberFormatFactory.visible() was suppose to return true.""); } /* * Tests the method public NumberFormat createFormat(Locale loc, int formatType) */ if (tf.createFormat(new Locale(""""), 0) != null) { errln(""NumberFormatFactory.createFormat(Locale loc, int formatType) "" + ""was suppose to return null""); } /* * Tests the method public NumberFormat createFormat(ULocale loc, int formatType) */ if (tf1.createFormat(new ULocale(""""), 0) != null) { errln(""NumberFormatFactory.createFormat(ULocale loc, int formatType) "" + ""was suppose to return null""); } } /* * Tests the class public static abstract class SimpleNumberFormatFactory extends NumberFormatFactory */ @Test public void TestSimpleNumberFormatFactory() { class TestSimpleNumberFormatFactory extends SimpleNumberFormatFactory { /* * Tests the method public SimpleNumberFormatFactory(Locale locale) */ TestSimpleNumberFormatFactory() { super(new Locale("""")); } } @SuppressWarnings(""unused"") TestSimpleNumberFormatFactory tsnff = new TestSimpleNumberFormatFactory(); } /* * Tests the method public static ULocale[] getAvailableLocales() */ @SuppressWarnings(""static-access"") @Test public void TestGetAvailableLocales() { // Tests when ""if (shim == null)"" is true @SuppressWarnings(""serial"") class TestGetAvailableLocales extends NumberFormat { @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public Number parse(String text, ParsePosition parsePosition) { return null; } } try { TestGetAvailableLocales test = new TestGetAvailableLocales(); test.getAvailableLocales(); } catch (Exception e) { errln(""NumberFormat.getAvailableLocales() was not suppose to "" + ""return an exception when getting getting available locales.""); } } /* * Tests the method public void setMinimumIntegerDigits(int newValue) */ @Test public void TestSetMinimumIntegerDigits() { NumberFormat nf = NumberFormat.getInstance(); // For valid array, it is displayed as {min value, max value} // Tests when ""if (minimumIntegerDigits > maximumIntegerDigits)"" is true int[][] cases = { { -1, 0 }, { 0, 1 }, { 1, 0 }, { 2, 0 }, { 2, 1 }, { 10, 0 } }; int[] expectedMax = { 0, 1, 1, 2, 2, 10 }; if (cases.length != expectedMax.length) { errln(""Can't continue test case method TestSetMinimumIntegerDigits "" + ""since the test case arrays are unequal.""); } else { for (int i = 0; i < cases.length; i++) { nf.setMaximumIntegerDigits(cases[i][1]); nf.setMinimumIntegerDigits(cases[i][0]); if (nf.getMaximumIntegerDigits() != expectedMax[i]) { errln(""NumberFormat.setMinimumIntegerDigits(int newValue "" + ""did not return an expected result for parameter "" + cases[i][1] + "" and "" + cases[i][0] + "" and expected "" + expectedMax[i] + "" but got "" + nf.getMaximumIntegerDigits()); } } } } /* * Tests the method public int getRoundingMode() public void setRoundingMode(int roundingMode) */ @Test public void TestRoundingMode() { @SuppressWarnings(""serial"") class TestRoundingMode extends NumberFormat { @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } @Override public Number parse(String text, ParsePosition parsePosition) { return null; } } TestRoundingMode tgrm = new TestRoundingMode(); // Tests the function 'public void setRoundingMode(int roundingMode)' try { tgrm.setRoundingMode(0); errln(""NumberFormat.setRoundingMode(int) was suppose to return an exception""); } catch (Exception e) { } // Tests the function 'public int getRoundingMode()' try { tgrm.getRoundingMode(); errln(""NumberFormat.getRoundingMode() was suppose to return an exception""); } catch (Exception e) { } } /* * Testing lenient decimal/grouping separator parsing */ @Test public void TestLenientSymbolParsing() { DecimalFormat fmt = new DecimalFormat(); DecimalFormatSymbols sym = new DecimalFormatSymbols(); expect(fmt, ""12\u300234"", 12.34); // Ticket#7345 - case 1 // Even strict parsing, the decimal separator set in the symbols // should be successfully parsed. sym.setDecimalSeparator('\u3002'); // non-strict fmt.setDecimalFormatSymbols(sym); // strict - failed before the fix for #7345 fmt.setParseStrict(true); expect(fmt, ""23\u300245"", 23.45); fmt.setParseStrict(false); // Ticket#7345 - case 2 // Decimal separator variants other than DecimalFormatSymbols.decimalSeparator // should not hide the grouping separator DecimalFormatSymbols.groupingSeparator. sym.setDecimalSeparator('.'); sym.setGroupingSeparator(','); fmt.setDecimalFormatSymbols(sym); expect(fmt, ""1,234.56"", 1234.56); sym.setGroupingSeparator('\uFF61'); fmt.setDecimalFormatSymbols(sym); expect(fmt, ""2\uFF61345.67"", 2345.67); // Ticket#7128 // sym.setGroupingSeparator(','); fmt.setDecimalFormatSymbols(sym); String skipExtSepParse = ICUConfig.get(""android.icu.text.DecimalFormat.SkipExtendedSeparatorParsing"", ""false""); if (skipExtSepParse.equals(""true"")) { // When the property SkipExtendedSeparatorParsing is true, // DecimalFormat does not use the extended equivalent separator // data and only uses the one in DecimalFormatSymbols. expect(fmt, ""23 456"", 23); } else { // Lenient separator parsing is enabled by default. // A space character below is interpreted as a // group separator, even ',' is used as grouping // separator in the symbols. expect(fmt, ""12 345"", 12345); } } /* * Testing currency driven max/min fraction digits problem * reported by ticket#7282 */ @Test public void TestCurrencyFractionDigits() { double value = 99.12345; // Create currency instance NumberFormat cfmt = NumberFormat.getCurrencyInstance(new ULocale(""ja_JP"")); String text1 = cfmt.format(value); // Reset the same currency and format the test value again cfmt.setCurrency(cfmt.getCurrency()); String text2 = cfmt.format(value); // output1 and output2 must be identical if (!text1.equals(text2)) { errln(""NumberFormat.format() should return the same result - text1="" + text1 + "" text2="" + text2); } } /* * Testing rounding to negative zero problem * reported by ticket#7609 */ @Test public void TestNegZeroRounding() { DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(); df.setRoundingMode(MathContext.ROUND_HALF_UP); df.setMinimumFractionDigits(1); df.setMaximumFractionDigits(1); String text1 = df.format(-0.01); df.setRoundingIncrement(0.1); String text2 = df.format(-0.01); // output1 and output2 must be identical if (!text1.equals(text2)) { errln(""NumberFormat.format() should return the same result - text1="" + text1 + "" text2="" + text2); } } @Test public void TestCurrencyAmountCoverage() { CurrencyAmount ca, cb; try { ca = new CurrencyAmount(null, null); errln(""NullPointerException should have been thrown.""); } catch (NullPointerException ex) { } try { ca = new CurrencyAmount(new Integer(0), null); errln(""NullPointerException should have been thrown.""); } catch (NullPointerException ex) { } ca = new CurrencyAmount(new Integer(0), Currency.getInstance(new ULocale(""ja_JP""))); cb = new CurrencyAmount(new Integer(1), Currency.getInstance(new ULocale(""ja_JP""))); if (ca.equals(null)) { errln(""Comparison should return false.""); } if (!ca.equals(ca)) { errln(""Comparision should return true.""); } if (ca.equals(cb)) { errln(""Comparison should return false.""); } } @Test public void TestExponentParse() { ParsePosition parsePos = new ParsePosition(0); DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); DecimalFormat fmt = new DecimalFormat(""#####"", symbols); Number result = fmt.parse(""5.06e-27"", parsePos); if ( result.doubleValue() != 5.06E-27 || parsePos.getIndex() != 8) { errln(""ERROR: ERROR: parse failed - expected 5.06E-27, 8; got "" + result.doubleValue() + "", "" + parsePos.getIndex()); } } @Test public void TestExplicitParents() { // We use these for testing because decimal and grouping separators will be inherited from es_419 // starting with CLDR 2.0 String[] DATA = { ""es"", ""CO"", """", ""1.250,75"", ""es"", ""ES"", """", ""1.250,75"", ""es"", ""GQ"", """", ""1.250,75"", ""es"", ""MX"", """", ""1,250.75"", ""es"", ""US"", """", ""1,250.75"", ""es"", ""VE"", """", ""1.250,75"", }; for (int i=0; i "" + s); } else { errln(""FAIL: 1250.75 x "" + locale + "" => "" + s + "", expected "" + DATA[i+3]); } } } /* * Test case for #9240 * ICU4J 49.1 DecimalFormat did not clone the internal object holding * formatted text attribute information properly. Therefore, DecimalFormat * created by cloning may return incorrect results or may throw an exception * when formatToCharacterIterator is invoked from multiple threads. */ @Test public void TestFormatToCharacterIteratorThread() { final int COUNT = 10; DecimalFormat fmt1 = new DecimalFormat(""#0""); DecimalFormat fmt2 = (DecimalFormat)fmt1.clone(); int[] res1 = new int[COUNT]; int[] res2 = new int[COUNT]; Thread t1 = new Thread(new FormatCharItrTestThread(fmt1, 1, res1)); Thread t2 = new Thread(new FormatCharItrTestThread(fmt2, 100, res2)); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { //TODO } int val1 = res1[0]; int val2 = res2[0]; for (int i = 0; i < COUNT; i++) { if (res1[i] != val1) { errln(""Inconsistent first run limit in test thread 1""); } if (res2[i] != val2) { errln(""Inconsistent first run limit in test thread 2""); } } } @Test public void TestParseMaxDigits() { DecimalFormat fmt = new DecimalFormat(); String number = ""100000000000""; int newParseMax = number.length() - 1; fmt.setParseMaxDigits(-1); /* Default value is 1000 */ if (fmt.getParseMaxDigits() != 1000) { errln(""Fail valid value checking in setParseMaxDigits.""); } try { if (fmt.parse(number).doubleValue() == Float.POSITIVE_INFINITY) { errln(""Got Infinity but should NOT when parsing number: "" + number); } fmt.setParseMaxDigits(newParseMax); if (fmt.parse(number).doubleValue() != Float.POSITIVE_INFINITY) { errln(""Did not get Infinity but should when parsing number: "" + number); } } catch (ParseException ex) { } } private static class FormatCharItrTestThread implements Runnable { private final NumberFormat fmt; private final int num; private final int[] result; FormatCharItrTestThread(NumberFormat fmt, int num, int[] result) { this.fmt = fmt; this.num = num; this.result = result; } @Override public void run() { for (int i = 0; i < result.length; i++) { AttributedCharacterIterator acitr = fmt.formatToCharacterIterator(num); acitr.first(); result[i] = acitr.getRunLimit(); } } } @Test public void TestRoundingBehavior() { final Object[][] TEST_CASES = { { ULocale.US, // ULocale - null for default locale ""#.##"", // Pattern Integer.valueOf(BigDecimal.ROUND_DOWN), // Rounding Mode or null (implicit) Double.valueOf(0.0d), // Rounding increment, Double or BigDecimal, or null (implicit) Double.valueOf(123.4567d), // Input value, Long, Double, BigInteger or BigDecimal ""123.45"" // Expected result, null for exception }, { ULocale.US, ""#.##"", null, Double.valueOf(0.1d), Double.valueOf(123.4567d), ""123.5"" }, { ULocale.US, ""#.##"", Integer.valueOf(BigDecimal.ROUND_DOWN), Double.valueOf(0.1d), Double.valueOf(123.4567d), ""123.4"" }, { ULocale.US, ""#.##"", Integer.valueOf(BigDecimal.ROUND_UNNECESSARY), null, Double.valueOf(123.4567d), null }, { ULocale.US, ""#.##"", Integer.valueOf(BigDecimal.ROUND_DOWN), null, Long.valueOf(1234), ""1234"" }, }; int testNum = 1; for (Object[] testCase : TEST_CASES) { // 0: locale // 1: pattern ULocale locale = testCase[0] == null ? ULocale.getDefault() : (ULocale)testCase[0]; String pattern = (String)testCase[1]; DecimalFormat fmt = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(locale)); // 2: rounding mode Integer roundingMode = null; if (testCase[2] != null) { roundingMode = (Integer)testCase[2]; fmt.setRoundingMode(roundingMode); } // 3: rounding increment if (testCase[3] != null) { if (testCase[3] instanceof Double) { fmt.setRoundingIncrement((Double)testCase[3]); } else if (testCase[3] instanceof BigDecimal) { fmt.setRoundingIncrement((BigDecimal)testCase[3]); } else if (testCase[3] instanceof java.math.BigDecimal) { fmt.setRoundingIncrement((java.math.BigDecimal)testCase[3]); } } // 4: input number String s = null; boolean bException = false; try { s = fmt.format(testCase[4]); } catch (ArithmeticException e) { bException = true; } if (bException) { if (testCase[5] != null) { errln(""Test case #"" + testNum + "": ArithmeticException was thrown.""); } } else { if (testCase[5] == null) { errln(""Test case #"" + testNum + "": ArithmeticException must be thrown, but got formatted result: "" + s); } else { assertEquals(""Test case #"" + testNum, testCase[5], s); } } testNum++; } } @Test public void TestSignificantDigits() { double input[] = { 0, 0, 123, -123, 12345, -12345, 123.45, -123.45, 123.44501, -123.44501, 0.001234, -0.001234, 0.00000000123, -0.00000000123, 0.0000000000000000000123, -0.0000000000000000000123, 1.2, -1.2, 0.0000000012344501, -0.0000000012344501, 123445.01, -123445.01, 12344501000000000000000000000000000.0, -12344501000000000000000000000000000.0, }; String[] expected = { ""0.00"", ""0.00"", ""123"", ""-123"", ""12345"", ""-12345"", ""123.45"", ""-123.45"", ""123.45"", ""-123.45"", ""0.001234"", ""-0.001234"", ""0.00000000123"", ""-0.00000000123"", ""0.0000000000000000000123"", ""-0.0000000000000000000123"", ""1.20"", ""-1.20"", ""0.0000000012345"", ""-0.0000000012345"", ""123450"", ""-123450"", ""12345000000000000000000000000000000"", ""-12345000000000000000000000000000000"", }; DecimalFormat numberFormat = (DecimalFormat) NumberFormat.getInstance(ULocale.US); numberFormat.setSignificantDigitsUsed(true); numberFormat.setMinimumSignificantDigits(3); numberFormat.setMaximumSignificantDigits(5); numberFormat.setGroupingUsed(false); for (int i = 0; i < input.length; i++) { assertEquals(""TestSignificantDigits"", expected[i], numberFormat.format(input[i])); } } @Test public void TestBug9936() { DecimalFormat numberFormat = (DecimalFormat) NumberFormat.getInstance(ULocale.US); assertFalse("""", numberFormat.areSignificantDigitsUsed()); numberFormat.setSignificantDigitsUsed(true); assertTrue("""", numberFormat.areSignificantDigitsUsed()); numberFormat.setSignificantDigitsUsed(false); assertFalse("""", numberFormat.areSignificantDigitsUsed()); numberFormat.setMinimumSignificantDigits(3); assertTrue("""", numberFormat.areSignificantDigitsUsed()); numberFormat.setSignificantDigitsUsed(false); numberFormat.setMaximumSignificantDigits(6); assertTrue("""", numberFormat.areSignificantDigitsUsed()); } @Test public void TestShowZero() { DecimalFormat numberFormat = (DecimalFormat) NumberFormat.getInstance(ULocale.US); numberFormat.setSignificantDigitsUsed(true); numberFormat.setMaximumSignificantDigits(3); assertEquals(""TestShowZero"", ""0"", numberFormat.format(0.0)); } @Test public void TestCurrencyPlurals() { String[][] tests = { {""en"", ""USD"", ""1"", ""1 US dollar""}, {""en"", ""USD"", ""1.0"", ""1.0 US dollars""}, {""en"", ""USD"", ""1.00"", ""1.00 US dollars""}, {""en"", ""USD"", ""1.99"", ""1.99 US dollars""}, {""en"", ""AUD"", ""1"", ""1 Australian dollar""}, {""en"", ""AUD"", ""1.00"", ""1.00 Australian dollars""}, {""sl"", ""USD"", ""1"", ""1 ameri\u0161ki dolar""}, {""sl"", ""USD"", ""2"", ""2 ameri\u0161ka dolarja""}, {""sl"", ""USD"", ""3"", ""3 ameri\u0161ki dolarji""}, {""sl"", ""USD"", ""5"", ""5 ameriških dolarjev""}, {""fr"", ""USD"", ""1.99"", ""1,99 dollar des États-Unis""}, {""ru"", ""RUB"", ""1"", ""1 \u0440\u043E\u0441\u0441\u0438\u0439\u0441\u043A\u0438\u0439 \u0440\u0443\u0431\u043B\u044C""}, {""ru"", ""RUB"", ""2"", ""2 \u0440\u043E\u0441\u0441\u0438\u0439\u0441\u043A\u0438\u0445 \u0440\u0443\u0431\u043B\u044F""}, {""ru"", ""RUB"", ""5"", ""5 \u0440\u043E\u0441\u0441\u0438\u0439\u0441\u043A\u0438\u0445 \u0440\u0443\u0431\u043B\u0435\u0439""}, }; for (String test[] : tests) { DecimalFormat numberFormat = (DecimalFormat) DecimalFormat.getInstance(new ULocale(test[0]), NumberFormat.PLURALCURRENCYSTYLE); numberFormat.setCurrency(Currency.getInstance(test[1])); double number = Double.parseDouble(test[2]); int dotPos = test[2].indexOf('.'); int decimals = dotPos < 0 ? 0 : test[2].length() - dotPos - 1; int digits = dotPos < 0 ? test[2].length() : test[2].length() - 1; numberFormat.setMaximumFractionDigits(decimals); numberFormat.setMinimumFractionDigits(decimals); String actual = numberFormat.format(number); assertEquals(test[0] + ""\t"" + test[1] + ""\t"" + test[2], test[3], actual); numberFormat.setMaximumSignificantDigits(digits); numberFormat.setMinimumSignificantDigits(digits); actual = numberFormat.format(number); assertEquals(test[0] + ""\t"" + test[1] + ""\t"" + test[2], test[3], actual); } } @Test public void TestCustomCurrencySignAndSeparator() { DecimalFormatSymbols custom = new DecimalFormatSymbols(ULocale.US); custom.setCurrencySymbol(""*""); custom.setMonetaryGroupingSeparator('^'); custom.setMonetaryDecimalSeparator(':'); DecimalFormat fmt = new DecimalFormat(""\u00A4 #,##0.00"", custom); final String numstr = ""* 1^234:56""; expect2(fmt, 1234.56, numstr); } @Test public void TestParseSignsAndMarks() { class SignsAndMarksItem { public String locale; public boolean lenient; public String numString; public double value; // Simple constructor public SignsAndMarksItem(String loc, boolean lnt, String numStr, double val) { locale = loc; lenient = lnt; numString = numStr; value = val; } }; final SignsAndMarksItem[] items = { // *** Note, ICU4J lenient number parsing does not handle arbitrary whitespace, but can // treat some whitespace as a grouping separator. The cases marked *** below depend // on isGroupingUsed() being set for the locale, which in turn depends on grouping // separators being present in the decimalFormat pattern for the locale (& num sys). // // locale lenient numString value new SignsAndMarksItem(""en"", false, ""12"", 12 ), new SignsAndMarksItem(""en"", true, ""12"", 12 ), new SignsAndMarksItem(""en"", false, ""-23"", -23 ), new SignsAndMarksItem(""en"", true, ""-23"", -23 ), new SignsAndMarksItem(""en"", true, ""- 23"", -23 ), // *** new SignsAndMarksItem(""en"", false, ""\u200E-23"", -23 ), new SignsAndMarksItem(""en"", true, ""\u200E-23"", -23 ), new SignsAndMarksItem(""en"", true, ""\u200E- 23"", -23 ), // *** new SignsAndMarksItem(""en@numbers=arab"", false, ""\u0663\u0664"", 34 ), new SignsAndMarksItem(""en@numbers=arab"", true, ""\u0663\u0664"", 34 ), new SignsAndMarksItem(""en@numbers=arab"", false, ""-\u0664\u0665"", -45 ), new SignsAndMarksItem(""en@numbers=arab"", true, ""-\u0664\u0665"", -45 ), new SignsAndMarksItem(""en@numbers=arab"", true, ""- \u0664\u0665"", -45 ), // *** new SignsAndMarksItem(""en@numbers=arab"", false, ""\u200F-\u0664\u0665"", -45 ), new SignsAndMarksItem(""en@numbers=arab"", true, ""\u200F-\u0664\u0665"", -45 ), new SignsAndMarksItem(""en@numbers=arab"", true, ""\u200F- \u0664\u0665"", -45 ), // *** new SignsAndMarksItem(""en@numbers=arabext"", false, ""\u06F5\u06F6"", 56 ), new SignsAndMarksItem(""en@numbers=arabext"", true, ""\u06F5\u06F6"", 56 ), new SignsAndMarksItem(""en@numbers=arabext"", false, ""-\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""en@numbers=arabext"", true, ""-\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""en@numbers=arabext"", true, ""- \u06F6\u06F7"", -67 ), // *** new SignsAndMarksItem(""en@numbers=arabext"", false, ""\u200E-\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""en@numbers=arabext"", true, ""\u200E-\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""en@numbers=arabext"", true, ""\u200E-\u200E \u06F6\u06F7"", -67 ), // *** new SignsAndMarksItem(""he"", false, ""12"", 12 ), new SignsAndMarksItem(""he"", true, ""12"", 12 ), new SignsAndMarksItem(""he"", false, ""-23"", -23 ), new SignsAndMarksItem(""he"", true, ""-23"", -23 ), new SignsAndMarksItem(""he"", true, ""- 23"", -23 ), // *** new SignsAndMarksItem(""he"", false, ""\u200E-23"", -23 ), new SignsAndMarksItem(""he"", true, ""\u200E-23"", -23 ), new SignsAndMarksItem(""he"", true, ""\u200E- 23"", -23 ), // *** new SignsAndMarksItem(""ar"", false, ""\u0663\u0664"", 34 ), new SignsAndMarksItem(""ar"", true, ""\u0663\u0664"", 34 ), new SignsAndMarksItem(""ar"", false, ""-\u0664\u0665"", -45 ), new SignsAndMarksItem(""ar"", true, ""-\u0664\u0665"", -45 ), new SignsAndMarksItem(""ar"", true, ""- \u0664\u0665"", -45 ), // *** new SignsAndMarksItem(""ar"", false, ""\u200F-\u0664\u0665"", -45 ), new SignsAndMarksItem(""ar"", true, ""\u200F-\u0664\u0665"", -45 ), new SignsAndMarksItem(""ar"", true, ""\u200F- \u0664\u0665"", -45 ), // *** new SignsAndMarksItem(""ar_MA"", false, ""12"", 12 ), new SignsAndMarksItem(""ar_MA"", true, ""12"", 12 ), new SignsAndMarksItem(""ar_MA"", false, ""-23"", -23 ), new SignsAndMarksItem(""ar_MA"", true, ""-23"", -23 ), new SignsAndMarksItem(""ar_MA"", true, ""- 23"", -23 ), // *** new SignsAndMarksItem(""ar_MA"", false, ""\u200E-23"", -23 ), new SignsAndMarksItem(""ar_MA"", true, ""\u200E-23"", -23 ), new SignsAndMarksItem(""ar_MA"", true, ""\u200E- 23"", -23 ), // *** new SignsAndMarksItem(""fa"", false, ""\u06F5\u06F6"", 56 ), new SignsAndMarksItem(""fa"", true, ""\u06F5\u06F6"", 56 ), new SignsAndMarksItem(""fa"", false, ""\u2212\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""fa"", true, ""\u2212\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""fa"", true, ""\u2212 \u06F6\u06F7"", -67 ), // *** new SignsAndMarksItem(""fa"", false, ""\u200E\u2212\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""fa"", true, ""\u200E\u2212\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""fa"", true, ""\u200E\u2212\u200E \u06F6\u06F7"", -67 ), // *** new SignsAndMarksItem(""ps"", false, ""\u06F5\u06F6"", 56 ), new SignsAndMarksItem(""ps"", true, ""\u06F5\u06F6"", 56 ), new SignsAndMarksItem(""ps"", false, ""-\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""ps"", true, ""-\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""ps"", true, ""- \u06F6\u06F7"", -67 ), // *** new SignsAndMarksItem(""ps"", false, ""\u200E-\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""ps"", true, ""\u200E-\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""ps"", true, ""\u200E-\u200E \u06F6\u06F7"", -67 ), // *** new SignsAndMarksItem(""ps"", false, ""-\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""ps"", true, ""-\u200E\u06F6\u06F7"", -67 ), new SignsAndMarksItem(""ps"", true, ""-\u200E \u06F6\u06F7"", -67 ), // *** }; for (SignsAndMarksItem item: items) { ULocale locale = new ULocale(item.locale); NumberFormat numfmt = NumberFormat.getInstance(locale); if (numfmt != null) { numfmt.setParseStrict(!item.lenient); ParsePosition ppos = new ParsePosition(0); Number num = numfmt.parse(item.numString, ppos); if (num != null && ppos.getIndex() == item.numString.length()) { double parsedValue = num.doubleValue(); if (parsedValue != item.value) { errln(""FAIL: locale "" + item.locale + "", lenient "" + item.lenient + "", parse of \"""" + item.numString + ""\"" gives value "" + parsedValue); } } else { errln(""FAIL: locale "" + item.locale + "", lenient "" + item.lenient + "", parse of \"""" + item.numString + ""\"" gives position "" + ppos.getIndex()); } } else { errln(""FAIL: NumberFormat.getInstance for locale "" + item.locale); } } } @Test public void TestContext() { // just a minimal sanity check for now NumberFormat nfmt = NumberFormat.getInstance(); DisplayContext context = nfmt.getContext(DisplayContext.Type.CAPITALIZATION); if (context != DisplayContext.CAPITALIZATION_NONE) { errln(""FAIL: Initial NumberFormat.getContext() is not CAPITALIZATION_NONE""); } nfmt.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE); context = nfmt.getContext(DisplayContext.Type.CAPITALIZATION); if (context != DisplayContext.CAPITALIZATION_FOR_STANDALONE) { errln(""FAIL: NumberFormat.getContext() does not return the value set, CAPITALIZATION_FOR_STANDALONE""); } } @Test public void TestAccountingCurrency() { String[][] tests = { //locale num curr fmt per loc curr std fmt curr acct fmt rt {""en_US"", ""1234.5"", ""$1,234.50"", ""$1,234.50"", ""$1,234.50"", ""true""}, {""en_US@cf=account"", ""1234.5"", ""$1,234.50"", ""$1,234.50"", ""$1,234.50"", ""true""}, {""en_US"", ""-1234.5"", ""-$1,234.50"", ""-$1,234.50"", ""($1,234.50)"", ""true""}, {""en_US@cf=standard"", ""-1234.5"", ""-$1,234.50"", ""-$1,234.50"", ""($1,234.50)"", ""true""}, {""en_US@cf=account"", ""-1234.5"", ""($1,234.50)"", ""-$1,234.50"", ""($1,234.50)"", ""true""}, {""en_US"", ""0"", ""$0.00"", ""$0.00"", ""$0.00"", ""true""}, {""en_US"", ""-0.2"", ""-$0.20"", ""-$0.20"", ""($0.20)"", ""true""}, {""en_US@cf=standard"", ""-0.2"", ""-$0.20"", ""-$0.20"", ""($0.20)"", ""true""}, {""en_US@cf=account"", ""-0.2"", ""($0.20)"", ""-$0.20"", ""($0.20)"", ""true""}, {""ja_JP"", ""10000"", ""¥10,000"", ""¥10,000"", ""¥10,000"", ""true"" }, {""ja_JP"", ""-1000.5"", ""-¥1,000"", ""-¥1,000"", ""(¥1,000)"", ""false""}, {""ja_JP@cf=account"", ""-1000.5"", ""(¥1,000)"", ""-¥1,000"", ""(¥1,000)"", ""false""}, {""de_DE"", ""-23456.7"", ""-23.456,70\u00A0€"", ""-23.456,70\u00A0€"", ""-23.456,70\u00A0€"", ""true"" }, }; for (String[] data : tests) { ULocale loc = new ULocale(data[0]); double num = Double.parseDouble(data[1]); String fmtPerLocExpected = data[2]; String fmtStandardExpected = data[3]; String fmtAccountExpected = data[4]; boolean rt = Boolean.parseBoolean(data[5]); NumberFormat fmtPerLoc = NumberFormat.getInstance(loc, NumberFormat.CURRENCYSTYLE); expect(fmtPerLoc, num, fmtPerLocExpected, rt); NumberFormat fmtStandard = NumberFormat.getInstance(loc, NumberFormat.STANDARDCURRENCYSTYLE); expect(fmtStandard, num, fmtStandardExpected, rt); NumberFormat fmtAccount = NumberFormat.getInstance(loc, NumberFormat.ACCOUNTINGCURRENCYSTYLE); expect(fmtAccount, num, fmtAccountExpected, rt); } } @Test public void TestCurrencyUsage() { // the 1st one is checking setter/getter, while the 2nd one checks for getInstance // compare the Currency and Currency Cash Digits // Note that as of CLDR 26: // * TWD switches from 0 decimals to 2; PKR still has 0, so change test to that // * CAD rounds to .05 in the cash style only. for (int i = 0; i < 2; i++) { String original_expected = ""PKR124""; DecimalFormat custom = null; if (i == 0) { custom = (DecimalFormat) DecimalFormat.getInstance(new ULocale(""en_US@currency=PKR""), DecimalFormat.CURRENCYSTYLE); String original = custom.format(123.567); assertEquals(""Test Currency Context"", original_expected, original); // test the getter assertEquals(""Test Currency Context Purpose"", custom.getCurrencyUsage(), Currency.CurrencyUsage.STANDARD); custom.setCurrencyUsage(Currency.CurrencyUsage.CASH); assertEquals(""Test Currency Context Purpose"", custom.getCurrencyUsage(), Currency.CurrencyUsage.CASH); } else { custom = (DecimalFormat) DecimalFormat.getInstance(new ULocale(""en_US@currency=PKR""), DecimalFormat.CASHCURRENCYSTYLE); // test the getter assertEquals(""Test Currency Context Purpose"", custom.getCurrencyUsage(), Currency.CurrencyUsage.CASH); } String cash_currency = custom.format(123.567); String cash_currency_expected = ""PKR124""; assertEquals(""Test Currency Context"", cash_currency_expected, cash_currency); } // the 1st one is checking setter/getter, while the 2nd one checks for getInstance // compare the Currency and Currency Cash Rounding for (int i = 0; i < 2; i++) { String original_rounding_expected = ""CA$123.57""; DecimalFormat fmt = null; if (i == 0) { fmt = (DecimalFormat) DecimalFormat.getInstance(new ULocale(""en_US@currency=CAD""), DecimalFormat.CURRENCYSTYLE); String original_rounding = fmt.format(123.566); assertEquals(""Test Currency Context"", original_rounding_expected, original_rounding); fmt.setCurrencyUsage(Currency.CurrencyUsage.CASH); } else { fmt = (DecimalFormat) DecimalFormat.getInstance(new ULocale(""en_US@currency=CAD""), DecimalFormat.CASHCURRENCYSTYLE); } String cash_rounding_currency = fmt.format(123.567); String cash__rounding_currency_expected = ""CA$123.55""; assertEquals(""Test Currency Context"", cash__rounding_currency_expected, cash_rounding_currency); } // the 1st one is checking setter/getter, while the 2nd one checks for getInstance // Test the currency change for (int i = 0; i < 2; i++) { DecimalFormat fmt2 = null; if (i == 1) { fmt2 = (DecimalFormat) NumberFormat.getInstance(new ULocale(""en_US@currency=JPY""), NumberFormat.CURRENCYSTYLE); fmt2.setCurrencyUsage(Currency.CurrencyUsage.CASH); } else { fmt2 = (DecimalFormat) NumberFormat.getInstance(new ULocale(""en_US@currency=JPY""), NumberFormat.CASHCURRENCYSTYLE); } fmt2.setCurrency(Currency.getInstance(""PKR"")); String PKR_changed = fmt2.format(123.567); String PKR_changed_expected = ""PKR124""; assertEquals(""Test Currency Context"", PKR_changed_expected, PKR_changed); } } @Test public void TestParseRequiredDecimalPoint() { String[] testPattern = { ""00.####"", ""00.0"", ""00"" }; String value2Parse = ""99""; double parseValue = 99; DecimalFormat parser = new DecimalFormat(); double result; boolean hasDecimalPoint; for (int i = 0; i < testPattern.length; i++) { parser.applyPattern(testPattern[i]); hasDecimalPoint = testPattern[i].contains("".""); parser.setDecimalPatternMatchRequired(false); try { result = parser.parse(value2Parse).doubleValue(); assertEquals(""wrong parsed value"", parseValue, result); } catch (ParseException e) { TestFmwk.errln(""Parsing "" + value2Parse + "" should have succeeded with "" + testPattern[i] + "" and isDecimalPointMatchRequired set to: "" + parser.isDecimalPatternMatchRequired()); } parser.setDecimalPatternMatchRequired(true); try { result = parser.parse(value2Parse).doubleValue(); if(hasDecimalPoint){ TestFmwk.errln(""Parsing "" + value2Parse + "" should NOT have succeeded with "" + testPattern[i] + "" and isDecimalPointMatchRequired set to: "" + parser.isDecimalPatternMatchRequired()); } } catch (ParseException e) { // OK, should fail } } } //TODO(junit): investigate @Test public void TestDataDrivenICU() { DataDrivenNumberFormatTestUtility.runSuite( ""numberformattestspecification.txt"", ICU); } //TODO(junit): investigate @Test public void TestDataDrivenJDK() { // Android patch: Android java.text.DecimalFormat is actually ICU. if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return; // Android patch end. DataDrivenNumberFormatTestUtility.runSuite( ""numberformattestspecification.txt"", JDK); } @Test public void TestCurrFmtNegSameAsPositive() { DecimalFormatSymbols decfmtsym = DecimalFormatSymbols.getInstance(Locale.US); decfmtsym.setMinusSign('\u200B'); // ZERO WIDTH SPACE, in ICU4J cannot set to empty string DecimalFormat decfmt = new DecimalFormat(""\u00A4#,##0.00;\u00A4#,##0.00"", decfmtsym); String currFmtResult = decfmt.format(-100.0); if (!currFmtResult.equals(""\u200B$100.00"")) { errln(""decfmt.toPattern results wrong, expected \u200B$100.00, got "" + currFmtResult); } } @Test public void TestNumberFormatTestDataToString() { new NumberFormatTestData().toString(); } // Testing for Issue 11805. @Test public void TestFormatToCharacterIteratorIssue11805 () { final double number = -350.76; DecimalFormat dfUS = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.US); String strUS = dfUS.format(number); Set resultUS = dfUS.formatToCharacterIterator(number).getAllAttributeKeys(); assertEquals(""Negative US Results: "" + strUS, 5, resultUS.size()); // For each test, add assert that all the fields are present and in the right spot. // TODO: Add tests for identify and position of each field, as in IntlTestDecimalFormatAPIC. DecimalFormat dfDE = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.GERMANY); String strDE = dfDE.format(number); Set resultDE = dfDE.formatToCharacterIterator(number).getAllAttributeKeys(); assertEquals(""Negative DE Results: "" + strDE, 5, resultDE.size()); DecimalFormat dfIN = (DecimalFormat) DecimalFormat.getCurrencyInstance(new Locale(""hi"", ""in"")); String strIN = dfIN.format(number); Set resultIN = dfIN.formatToCharacterIterator(number).getAllAttributeKeys(); assertEquals(""Negative IN Results: "" + strIN, 5, resultIN.size()); DecimalFormat dfJP = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.JAPAN); String strJP = dfJP.format(number); Set resultJP = dfJP.formatToCharacterIterator(number).getAllAttributeKeys(); assertEquals(""Negative JA Results: "" + strJP, 3, resultJP.size()); DecimalFormat dfGB = (DecimalFormat) DecimalFormat.getCurrencyInstance(new Locale(""en"", ""gb"")); String strGB = dfGB.format(number); Set resultGB = dfGB.formatToCharacterIterator(number).getAllAttributeKeys(); assertEquals(""Negative GB Results: "" + strGB , 5, resultGB.size()); DecimalFormat dfPlural = (DecimalFormat) NumberFormat.getInstance(new Locale(""en"", ""gb""), NumberFormat.PLURALCURRENCYSTYLE); strGB = dfPlural.format(number); resultGB = dfPlural.formatToCharacterIterator(number).getAllAttributeKeys(); assertEquals(""Negative GB Results: "" + strGB , 5, resultGB.size()); strGB = dfPlural.format(1); resultGB = dfPlural.formatToCharacterIterator(1).getAllAttributeKeys(); assertEquals(""Negative GB Results: "" + strGB , 4, resultGB.size()); // Test output with unit value. DecimalFormat auPlural = (DecimalFormat) NumberFormat.getInstance(new Locale(""en"", ""au""), NumberFormat.PLURALCURRENCYSTYLE); String strAU = auPlural.format(1L); Set resultAU = auPlural.formatToCharacterIterator(1L).getAllAttributeKeys(); assertEquals(""Unit AU Result: "" + strAU , 4, resultAU.size()); // Verify Permille fields. DecimalFormatSymbols sym = new DecimalFormatSymbols(new Locale(""en"", ""gb"")); DecimalFormat dfPermille = new DecimalFormat(""####0.##\u2030"", sym); strGB = dfPermille.format(number); resultGB = dfPermille.formatToCharacterIterator(number).getAllAttributeKeys(); assertEquals(""Negative GB Permille Results: "" + strGB , 3, resultGB.size()); } // Testing for Issue 11808. @Test public void TestRoundUnnecessarytIssue11808 () { DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); StringBuffer result = new StringBuffer(""""); df.setRoundingMode(BigDecimal.ROUND_UNNECESSARY); df.applyPattern(""00.0#E0""); try { df.format(99999.0, result, new FieldPosition(0)); fail(""Missing ArithmeticException for double: "" + result); } catch (ArithmeticException expected) { // The exception should be thrown, since rounding is needed. } try { result = df.format(99999, result, new FieldPosition(0)); fail(""Missing ArithmeticException for int: "" + result); } catch (ArithmeticException expected) { // The exception should be thrown, since rounding is needed. } try { result = df.format(new BigInteger(""999999""), result, new FieldPosition(0)); fail(""Missing ArithmeticException for BigInteger: "" + result); } catch (ArithmeticException expected) { // The exception should be thrown, since rounding is needed. } try { result = df.format(new BigDecimal(""99999""), result, new FieldPosition(0)); fail(""Missing ArithmeticException for BigDecimal: "" + result); } catch (ArithmeticException expected) { // The exception should be thrown, since rounding is needed. } try { result = df.format(new BigDecimal(""-99999""), result, new FieldPosition(0)); fail(""Missing ArithmeticException for BigDecimal: "" + result); } catch (ArithmeticException expected) { // The exception should be thrown, since rounding is needed. } } // Testing for Issue 11735. @Test public void TestNPEIssue11735() { DecimalFormat fmt = new DecimalFormat(""0"", new DecimalFormatSymbols(new ULocale(""en""))); ParsePosition ppos = new ParsePosition(0); assertEquals(""Currency symbol missing in parse. Expect null result."", fmt.parseCurrency(""53.45"", ppos), null); } private void CompareAttributedCharacterFormatOutput(AttributedCharacterIterator iterator, List expected, String formattedOutput) { List result = new ArrayList(); while (iterator.getIndex() != iterator.getEndIndex()) { int start = iterator.getRunStart(); int end = iterator.getRunLimit(); Iterator it = iterator.getAttributes().keySet().iterator(); AttributedCharacterIterator.Attribute attribute = (AttributedCharacterIterator.Attribute) it.next(); Object value = iterator.getAttribute(attribute); result.add(new FieldContainer(start, end, attribute, value)); iterator.setIndex(end); } assertEquals(""Comparing vector length for "" + formattedOutput, expected.size(), result.size()); if (!expected.containsAll(result)) { // Print information on the differences. for (int i = 0; i < expected.size(); i++) { System.out.println("" expected["" + i + ""] ="" + expected.get(i).start + "" "" + expected.get(i).end + "" "" + expected.get(i).attribute + "" "" + expected.get(i).value); System.out.println("" result["" + i + ""] ="" + result.get(i).start + "" "" + result.get(i).end + "" "" + result.get(i).attribute + "" "" + result.get(i).value); } } // TODO: restore when #11914 is fixed. // assertTrue(""Comparing vector results for "" + formattedOutput, // expected.containsAll(result)); } // Testing for Issue 11914, missing FieldPositions for some field types. @Test public void TestNPEIssue11914() { // First test: Double value with grouping separators. List v1 = new ArrayList(7); v1.add(new FieldContainer(0, 3, NumberFormat.Field.INTEGER)); v1.add(new FieldContainer(3, 4, NumberFormat.Field.GROUPING_SEPARATOR)); v1.add(new FieldContainer(4, 7, NumberFormat.Field.INTEGER)); v1.add(new FieldContainer(7, 8, NumberFormat.Field.GROUPING_SEPARATOR)); v1.add(new FieldContainer(8, 11, NumberFormat.Field.INTEGER)); v1.add(new FieldContainer(11, 12, NumberFormat.Field.DECIMAL_SEPARATOR)); v1.add(new FieldContainer(12, 15, NumberFormat.Field.FRACTION)); Number number = new Double(123456789.9753); ULocale usLoc = new ULocale(""en-US""); DecimalFormatSymbols US = new DecimalFormatSymbols(usLoc); NumberFormat outFmt = NumberFormat.getNumberInstance(usLoc); String numFmtted = outFmt.format(number); AttributedCharacterIterator iterator = outFmt.formatToCharacterIterator(number); CompareAttributedCharacterFormatOutput(iterator, v1, numFmtted); // Second test: Double with scientific notation formatting. List v2 = new ArrayList(7); v2.add(new FieldContainer(0, 1, NumberFormat.Field.INTEGER)); v2.add(new FieldContainer(1, 2, NumberFormat.Field.DECIMAL_SEPARATOR)); v2.add(new FieldContainer(2, 5, NumberFormat.Field.FRACTION)); v2.add(new FieldContainer(5, 6, NumberFormat.Field.EXPONENT_SYMBOL)); v2.add(new FieldContainer(6, 7, NumberFormat.Field.EXPONENT_SIGN)); v2.add(new FieldContainer(7, 8, NumberFormat.Field.EXPONENT)); DecimalFormat fmt2 = new DecimalFormat(""0.###E+0"", US); numFmtted = fmt2.format(number); iterator = fmt2.formatToCharacterIterator(number); CompareAttributedCharacterFormatOutput(iterator, v2, numFmtted); // Third test. BigInteger with grouping separators. List v3 = new ArrayList(7); v3.add(new FieldContainer(0, 1, NumberFormat.Field.SIGN)); v3.add(new FieldContainer(1, 2, NumberFormat.Field.INTEGER)); v3.add(new FieldContainer(2, 3, NumberFormat.Field.GROUPING_SEPARATOR)); v3.add(new FieldContainer(3, 6, NumberFormat.Field.INTEGER)); v3.add(new FieldContainer(6, 7, NumberFormat.Field.GROUPING_SEPARATOR)); v3.add(new FieldContainer(7, 10, NumberFormat.Field.INTEGER)); v3.add(new FieldContainer(10, 11, NumberFormat.Field.GROUPING_SEPARATOR)); v3.add(new FieldContainer(11, 14, NumberFormat.Field.INTEGER)); v3.add(new FieldContainer(14, 15, NumberFormat.Field.GROUPING_SEPARATOR)); v3.add(new FieldContainer(15, 18, NumberFormat.Field.INTEGER)); v3.add(new FieldContainer(18, 19, NumberFormat.Field.GROUPING_SEPARATOR)); v3.add(new FieldContainer(19, 22, NumberFormat.Field.INTEGER)); v3.add(new FieldContainer(22, 23, NumberFormat.Field.GROUPING_SEPARATOR)); v3.add(new FieldContainer(23, 26, NumberFormat.Field.INTEGER)); BigInteger bigNumberInt = new BigInteger(""-1234567890246813579""); String fmtNumberBigInt = outFmt.format(bigNumberInt); iterator = outFmt.formatToCharacterIterator(bigNumberInt); CompareAttributedCharacterFormatOutput(iterator, v3, fmtNumberBigInt); // Fourth test: BigDecimal with exponential formatting. List v4 = new ArrayList(7); v4.add(new FieldContainer(0, 1, NumberFormat.Field.SIGN)); v4.add(new FieldContainer(1, 2, NumberFormat.Field.INTEGER)); v4.add(new FieldContainer(2, 3, NumberFormat.Field.DECIMAL_SEPARATOR)); v4.add(new FieldContainer(3, 6, NumberFormat.Field.FRACTION)); v4.add(new FieldContainer(6, 7, NumberFormat.Field.EXPONENT_SYMBOL)); v4.add(new FieldContainer(7, 8, NumberFormat.Field.EXPONENT_SIGN)); v4.add(new FieldContainer(8, 9, NumberFormat.Field.EXPONENT)); java.math.BigDecimal numberBigD = new java.math.BigDecimal(-123456789); String fmtNumberBigDExp = fmt2.format(numberBigD); iterator = fmt2.formatToCharacterIterator(numberBigD); CompareAttributedCharacterFormatOutput(iterator, v4, fmtNumberBigDExp); } // Test that the decimal is shown even when there are no fractional digits @Test public void Test11621() throws Exception { String pat = ""0.##E0""; DecimalFormatSymbols icuSym = new DecimalFormatSymbols(Locale.US); DecimalFormat icuFmt = new DecimalFormat(pat, icuSym); icuFmt.setDecimalSeparatorAlwaysShown(true); String icu = ((NumberFormat)icuFmt).format(299792458); java.text.DecimalFormatSymbols jdkSym = new java.text.DecimalFormatSymbols(Locale.US); java.text.DecimalFormat jdkFmt = new java.text.DecimalFormat(pat,jdkSym); jdkFmt.setDecimalSeparatorAlwaysShown(true); String jdk = ((java.text.NumberFormat)jdkFmt).format(299792458); assertEquals(""ICU and JDK placement of decimal in exponent"", jdk, icu); } private void checkFormatWithField(String testInfo, Format format, Object object, String expected, Format.Field field, int begin, int end) { StringBuffer buffer = new StringBuffer(); FieldPosition pos = new FieldPosition(field); format.format(object, buffer, pos); assertEquals(""Test "" + testInfo + "": incorrect formatted text"", expected, buffer.toString()); if (begin != pos.getBeginIndex() || end != pos.getEndIndex()) { assertEquals(""Index mismatch"", field + "" "" + begin + "".."" + end, pos.getFieldAttribute() + "" "" + pos.getBeginIndex() + "".."" + pos.getEndIndex()); } } @Test public void TestMissingFieldPositionsCurrency() { DecimalFormat formatter = (DecimalFormat) NumberFormat.getCurrencyInstance(ULocale.US); Number number = new Double(92314587.66); String result = ""$92,314,587.66""; checkFormatWithField(""currency"", formatter, number, result, NumberFormat.Field.CURRENCY, 0, 1); checkFormatWithField(""integer"", formatter, number, result, NumberFormat.Field.INTEGER, 1, 11); checkFormatWithField(""grouping separator"", formatter, number, result, NumberFormat.Field.GROUPING_SEPARATOR, 3, 4); checkFormatWithField(""decimal separator"", formatter, number, result, NumberFormat.Field.DECIMAL_SEPARATOR, 11, 12); checkFormatWithField(""fraction"", formatter, number, result, NumberFormat.Field.FRACTION, 12, 14); } @Test public void TestMissingFieldPositionsNegativeDouble() { // test for exponential fields with double DecimalFormatSymbols us_symbols = new DecimalFormatSymbols(ULocale.US); Number number = new Double(-12345678.90123); DecimalFormat formatter = new DecimalFormat(""0.#####E+00"", us_symbols); String numFmtted = formatter.format(number); checkFormatWithField(""sign"", formatter, number, numFmtted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", formatter, number, numFmtted, NumberFormat.Field.INTEGER, 1, 2); checkFormatWithField(""decimal separator"", formatter, number, numFmtted, NumberFormat.Field.DECIMAL_SEPARATOR, 2, 3); checkFormatWithField(""exponent symbol"", formatter, number, numFmtted, NumberFormat.Field.EXPONENT_SYMBOL, 8, 9); checkFormatWithField(""exponent sign"", formatter, number, numFmtted, NumberFormat.Field.EXPONENT_SIGN, 9, 10); checkFormatWithField(""exponent"", formatter, number, numFmtted, NumberFormat.Field.EXPONENT, 10, 12); } @Test public void TestMissingFieldPositionsPerCent() { // Check PERCENT DecimalFormat percentFormat = (DecimalFormat) NumberFormat.getPercentInstance(ULocale.US); Number number = new Double(-0.986); String numberFormatted = percentFormat.format(number); checkFormatWithField(""sign"", percentFormat, number, numberFormatted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", percentFormat, number, numberFormatted, NumberFormat.Field.INTEGER, 1, 3); checkFormatWithField(""percent"", percentFormat, number, numberFormatted, NumberFormat.Field.PERCENT, 3, 4); } @Test public void TestMissingFieldPositionsPerCentPattern() { // Check PERCENT with more digits DecimalFormatSymbols us_symbols = new DecimalFormatSymbols(ULocale.US); DecimalFormat fmtPercent = new DecimalFormat(""0.#####%"", us_symbols); Number number = new Double(-0.986); String numFmtted = fmtPercent.format(number); checkFormatWithField(""sign"", fmtPercent, number, numFmtted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", fmtPercent, number, numFmtted, NumberFormat.Field.INTEGER, 1, 3); checkFormatWithField(""decimal separator"", fmtPercent, number, numFmtted, NumberFormat.Field.DECIMAL_SEPARATOR, 3, 4); checkFormatWithField(""fraction"", fmtPercent, number, numFmtted, NumberFormat.Field.FRACTION, 4, 5); checkFormatWithField(""percent"", fmtPercent, number, numFmtted, NumberFormat.Field.PERCENT, 5, 6); } @Test public void TestMissingFieldPositionsPerMille() { // Check PERMILLE DecimalFormatSymbols us_symbols = new DecimalFormatSymbols(ULocale.US); DecimalFormat fmtPerMille = new DecimalFormat(""0.######‰"", us_symbols); Number numberPermille = new Double(-0.98654); String numFmtted = fmtPerMille.format(numberPermille); checkFormatWithField(""sign"", fmtPerMille, numberPermille, numFmtted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", fmtPerMille, numberPermille, numFmtted, NumberFormat.Field.INTEGER, 1, 4); checkFormatWithField(""decimal separator"", fmtPerMille, numberPermille, numFmtted, NumberFormat.Field.DECIMAL_SEPARATOR, 4, 5); checkFormatWithField(""fraction"", fmtPerMille, numberPermille, numFmtted, NumberFormat.Field.FRACTION, 5, 7); checkFormatWithField(""permille"", fmtPerMille, numberPermille, numFmtted, NumberFormat.Field.PERMILLE, 7, 8); } @Test public void TestMissingFieldPositionsNegativeBigInt() { DecimalFormatSymbols us_symbols = new DecimalFormatSymbols(ULocale.US); DecimalFormat formatter = new DecimalFormat(""0.#####E+0"", us_symbols); Number number = new BigDecimal(""-123456789987654321""); String bigDecFmtted = formatter.format(number); checkFormatWithField(""sign"", formatter, number, bigDecFmtted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", formatter, number, bigDecFmtted, NumberFormat.Field.INTEGER, 1, 2); checkFormatWithField(""decimal separator"", formatter, number, bigDecFmtted, NumberFormat.Field.DECIMAL_SEPARATOR, 2, 3); checkFormatWithField(""exponent symbol"", formatter, number, bigDecFmtted, NumberFormat.Field.EXPONENT_SYMBOL, 8, 9); checkFormatWithField(""exponent sign"", formatter, number, bigDecFmtted, NumberFormat.Field.EXPONENT_SIGN, 9, 10); checkFormatWithField(""exponent"", formatter, number, bigDecFmtted, NumberFormat.Field.EXPONENT, 10, 12); } @Test public void TestMissingFieldPositionsNegativeLong() { Number number = new Long(""-123456789987654321""); DecimalFormatSymbols us_symbols = new DecimalFormatSymbols(ULocale.US); DecimalFormat formatter = new DecimalFormat(""0.#####E+0"", us_symbols); String longFmtted = formatter.format(number); checkFormatWithField(""sign"", formatter, number, longFmtted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", formatter, number, longFmtted, NumberFormat.Field.INTEGER, 1, 2); checkFormatWithField(""decimal separator"", formatter, number, longFmtted, NumberFormat.Field.DECIMAL_SEPARATOR, 2, 3); checkFormatWithField(""exponent symbol"", formatter, number, longFmtted, NumberFormat.Field.EXPONENT_SYMBOL, 8, 9); checkFormatWithField(""exponent sign"", formatter, number, longFmtted, NumberFormat.Field.EXPONENT_SIGN, 9, 10); checkFormatWithField(""exponent"", formatter, number, longFmtted, NumberFormat.Field.EXPONENT, 10, 12); } @Test public void TestMissingFieldPositionsPositiveBigDec() { // Check complex positive;negative pattern. DecimalFormatSymbols us_symbols = new DecimalFormatSymbols(ULocale.US); DecimalFormat fmtPosNegSign = new DecimalFormat(""+0.####E+00;-0.#######E+0"", us_symbols); Number positiveExp = new Double(""9876543210""); String posExpFormatted = fmtPosNegSign.format(positiveExp); checkFormatWithField(""sign"", fmtPosNegSign, positiveExp, posExpFormatted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", fmtPosNegSign, positiveExp, posExpFormatted, NumberFormat.Field.INTEGER, 1, 2); checkFormatWithField(""decimal separator"", fmtPosNegSign, positiveExp, posExpFormatted, NumberFormat.Field.DECIMAL_SEPARATOR, 2, 3); checkFormatWithField(""fraction"", fmtPosNegSign, positiveExp, posExpFormatted, NumberFormat.Field.FRACTION, 3, 7); checkFormatWithField(""exponent symbol"", fmtPosNegSign, positiveExp, posExpFormatted, NumberFormat.Field.EXPONENT_SYMBOL, 7, 8); checkFormatWithField(""exponent sign"", fmtPosNegSign, positiveExp, posExpFormatted, NumberFormat.Field.EXPONENT_SIGN, 8, 9); checkFormatWithField(""exponent"", fmtPosNegSign, positiveExp, posExpFormatted, NumberFormat.Field.EXPONENT, 9, 11); } @Test public void TestMissingFieldPositionsNegativeBigDec() { // Check complex positive;negative pattern. DecimalFormatSymbols us_symbols = new DecimalFormatSymbols(ULocale.US); DecimalFormat fmtPosNegSign = new DecimalFormat(""+0.####E+00;-0.#######E+0"", us_symbols); Number negativeExp = new BigDecimal(""-0.000000987654321083""); String negExpFormatted = fmtPosNegSign.format(negativeExp); checkFormatWithField(""sign"", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.SIGN, 0, 1); checkFormatWithField(""integer"", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.INTEGER, 1, 2); checkFormatWithField(""decimal separator"", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.DECIMAL_SEPARATOR, 2, 3); checkFormatWithField(""fraction"", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.FRACTION, 3, 7); checkFormatWithField(""exponent symbol"", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.EXPONENT_SYMBOL, 7, 8); checkFormatWithField(""exponent sign"", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.EXPONENT_SIGN, 8, 9); checkFormatWithField(""exponent"", fmtPosNegSign, negativeExp, negExpFormatted, NumberFormat.Field.EXPONENT, 9, 11); } @Test public void TestStringSymbols() { DecimalFormatSymbols symbols = new DecimalFormatSymbols(ULocale.US); String[] customDigits = {""(0)"", ""(1)"", ""(2)"", ""(3)"", ""(4)"", ""(5)"", ""(6)"", ""(7)"", ""(8)"", ""(9)""}; symbols.setDigitStrings(customDigits); symbols.setDecimalSeparatorString(""~~""); symbols.setGroupingSeparatorString(""^^""); DecimalFormat fmt = new DecimalFormat(""#,##0.0#"", symbols); expect2(fmt, 1234567.89, ""(1)^^(2)(3)(4)^^(5)(6)(7)~~(8)(9)""); } } ","currpat " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.metadata.report.support; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.common.utils.ConfigUtils; import org.apache.dubbo.common.utils.JsonUtils; import org.apache.dubbo.common.utils.NamedThreadFactory; import org.apache.dubbo.metadata.definition.model.FullServiceDefinition; import org.apache.dubbo.metadata.definition.model.ServiceDefinition; import org.apache.dubbo.metadata.report.MetadataReport; import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum; import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier; import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier; import org.apache.dubbo.metrics.event.MetricsEventBus; import org.apache.dubbo.metrics.metadata.event.MetadataEvent; import org.apache.dubbo.rpc.model.ApplicationModel; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE; import static org.apache.dubbo.common.constants.CommonConstants.CYCLE_REPORT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.FILE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE; import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_LOCAL_FILE_CACHE_ENABLED; import static org.apache.dubbo.common.constants.CommonConstants.REPORT_DEFINITION_KEY; import static org.apache.dubbo.common.constants.CommonConstants.REPORT_METADATA_KEY; import static org.apache.dubbo.common.constants.CommonConstants.RETRY_PERIOD_KEY; import static org.apache.dubbo.common.constants.CommonConstants.RETRY_TIMES_KEY; import static org.apache.dubbo.common.constants.CommonConstants.SYNC_REPORT_KEY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROXY_FAILED_EXPORT_SERVICE; import static org.apache.dubbo.common.utils.StringUtils.replace; import static org.apache.dubbo.metadata.report.support.Constants.CACHE; import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT; import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_RETRY_PERIOD; import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_RETRY_TIMES; import static org.apache.dubbo.metadata.report.support.Constants.DUBBO_METADATA; import static org.apache.dubbo.metadata.report.support.Constants.USER_HOME; public abstract class AbstractMetadataReport implements MetadataReport { protected final static String DEFAULT_ROOT = ""dubbo""; private static final int ONE_DAY_IN_MILLISECONDS = 60 * 24 * 60 * 1000; private static final int FOUR_HOURS_IN_MILLISECONDS = 60 * 4 * 60 * 1000; // Log output protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); // Local disk cache, where the special key value.registries records the list of metadata centers, and the others are the list of notified service providers final Properties properties = new Properties(); private final ExecutorService reportCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory(""DubboSaveMetadataReport"", true)); final Map allMetadataReports = new ConcurrentHashMap<>(4); private final AtomicLong lastCacheChanged = new AtomicLong(); final Map failedReports = new ConcurrentHashMap<>(4); private URL reportURL; boolean syncReport; // Local disk cache file File file; private AtomicBoolean initialized = new AtomicBoolean(false); public MetadataReportRetry metadataReportRetry; private ScheduledExecutorService reportTimerScheduler; private final boolean reportMetadata; private final boolean reportDefinition; protected ApplicationModel applicationModel; public AbstractMetadataReport(URL reportServerURL) { setUrl(reportServerURL); applicationModel = reportServerURL.getOrDefaultApplicationModel(); boolean localCacheEnabled = reportServerURL.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true); // Start file save timer String defaultFilename = System.getProperty(USER_HOME) + DUBBO_METADATA + reportServerURL.getApplication() + ""-"" + replace(reportServerURL.getAddress(), "":"", ""-"") + CACHE; String filename = reportServerURL.getParameter(FILE_KEY, defaultFilename); File file = null; if (localCacheEnabled && ConfigUtils.isNotEmpty(filename)) { file = new File(filename); if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) { if (!file.getParentFile().mkdirs()) { throw new IllegalArgumentException(""Invalid service store file "" + file + "", cause: Failed to create directory "" + file.getParentFile() + ""!""); } } // if this file exists, firstly delete it. if (!initialized.getAndSet(true) && file.exists()) { file.delete(); } } this.file = file; loadProperties(); syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false); metadataReportRetry = new MetadataReportRetry(reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES), reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD)); // cycle report the data switch if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) { reportTimerScheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(""DubboMetadataReportTimer"", true)); reportTimerScheduler.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS); } this.reportMetadata = reportServerURL.getParameter(REPORT_METADATA_KEY, false); this.reportDefinition = reportServerURL.getParameter(REPORT_DEFINITION_KEY, true); } public URL getUrl() { return reportURL; } protected void setUrl(URL url) { if (url == null) { throw new IllegalArgumentException(""metadataReport url == null""); } this.reportURL = url; } private void doSaveProperties(long version) { if (version < lastCacheChanged.get()) { return; } if (file == null) { return; } // Save try { File lockfile = new File(file.getAbsolutePath() + "".lock""); if (!lockfile.exists()) { lockfile.createNewFile(); } try (RandomAccessFile raf = new RandomAccessFile(lockfile, ""rw""); FileChannel channel = raf.getChannel()) { FileLock lock = channel.tryLock(); if (lock == null) { throw new IOException(""Can not lock the metadataReport cache file "" + file.getAbsolutePath() + "", ignore and retry later, maybe multi java process use the file, please config: dubbo.metadata.file=xxx.properties""); } // Save try { if (!file.exists()) { file.createNewFile(); } Properties tmpProperties; if (!syncReport) { // When syncReport = false, properties.setProperty and properties.store are called from the same // thread(reportCacheExecutor), so deep copy is not required tmpProperties = properties; } else { // Using store method and setProperty method of the this.properties will cause lock contention // under multi-threading, so deep copy a new container tmpProperties = new Properties(); Set> entries = properties.entrySet(); for (Map.Entry entry : entries) { tmpProperties.setProperty((String) entry.getKey(), (String) entry.getValue()); } } try (FileOutputStream outputFile = new FileOutputStream(file)) { tmpProperties.store(outputFile, ""Dubbo metadataReport Cache""); } } finally { lock.release(); } } } catch (Throwable e) { if (version < lastCacheChanged.get()) { return; } else { reportCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet())); } logger.warn(COMMON_UNEXPECTED_EXCEPTION, """", """", ""Failed to save service store file, cause: "" + e.getMessage(), e); } } void loadProperties() { if (file != null && file.exists()) { try (InputStream in = new FileInputStream(file)) { properties.load(in); if (logger.isInfoEnabled()) { logger.info(""Load service store file "" + file + "", data: "" + properties); } } catch (Throwable e) { logger.warn(COMMON_UNEXPECTED_EXCEPTION, """", """", ""Failed to load service store file"" + file, e); } } } private void saveProperties(MetadataIdentifier metadataIdentifier, String value, boolean add, boolean sync) { if (file == null) { return; } try { if (add) { properties.setProperty(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), value); } else { properties.remove(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY)); } long version = lastCacheChanged.incrementAndGet(); if (sync) { new SaveProperties(version).run(); } else { reportCacheExecutor.execute(new SaveProperties(version)); } } catch (Throwable t) { logger.warn(COMMON_UNEXPECTED_EXCEPTION, """", """", t.getMessage(), t); } } @Override public String toString() { return getUrl().toString(); } private class SaveProperties implements Runnable { private long version; private SaveProperties(long version) { this.version = version; } @Override public void run() { doSaveProperties(version); } } @Override public void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) { if (syncReport) { storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition); } else { reportCacheExecutor.execute(() -> storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition)); } } private void storeProviderMetadataTask(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) { MetadataEvent metadataEvent = MetadataEvent.toServiceSubscribeEvent(applicationModel, providerMetadataIdentifier.getUniqueServiceName()); MetricsEventBus.post(metadataEvent, () -> { boolean result = true; try { if (logger.isInfoEnabled()) { logger.info(""store provider metadata. Identifier : "" + providerMetadataIdentifier + ""; definition: "" + serviceDefinition); } allMetadataReports.put(providerMetadataIdentifier, serviceDefinition); failedReports.remove(providerMetadataIdentifier); String data = JsonUtils.toJson(serviceDefinition); doStoreProviderMetadata(providerMetadataIdentifier, data); saveProperties(providerMetadataIdentifier, data, true, !syncReport); } catch (Exception e) { // retry again. If failed again, throw exception. failedReports.put(providerMetadataIdentifier, serviceDefinition); metadataReportRetry.startRetryTask(); logger.error(PROXY_FAILED_EXPORT_SERVICE, """", """", ""Failed to put provider metadata "" + providerMetadataIdentifier + "" in "" + serviceDefinition + "", cause: "" + e.getMessage(), e); result = false; } return result; }, aBoolean -> aBoolean ); } @Override public void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, Map serviceParameterMap) { if (syncReport) { storeConsumerMetadataTask(consumerMetadataIdentifier, serviceParameterMap); } else { reportCacheExecutor.execute(() -> storeConsumerMetadataTask(consumerMetadataIdentifier, serviceParameterMap)); } } protected void storeConsumerMetadataTask(MetadataIdentifier consumerMetadataIdentifier, Map serviceParameterMap) { try { if (logger.isInfoEnabled()) { logger.info(""store consumer metadata. Identifier : "" + consumerMetadataIdentifier + ""; definition: "" + serviceParameterMap); } allMetadataReports.put(consumerMetadataIdentifier, serviceParameterMap); failedReports.remove(consumerMetadataIdentifier); String data = JsonUtils.toJson(serviceParameterMap); doStoreConsumerMetadata(consumerMetadataIdentifier, data); saveProperties(consumerMetadataIdentifier, data, true, !syncReport); } catch (Exception e) { // retry again. If failed again, throw exception. failedReports.put(consumerMetadataIdentifier, serviceParameterMap); metadataReportRetry.startRetryTask(); logger.error(PROXY_FAILED_EXPORT_SERVICE, """", """", ""Failed to put consumer metadata "" + consumerMetadataIdentifier + ""; "" + serviceParameterMap + "", cause: "" + e.getMessage(), e); } } @Override public void destroy() { if (reportCacheExecutor != null) { reportCacheExecutor.shutdown(); } if (reportTimerScheduler != null) { reportTimerScheduler.shutdown(); } if (metadataReportRetry != null) { metadataReportRetry.destroy(); metadataReportRetry = null; } } @Override public void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) { if (syncReport) { doSaveMetadata(metadataIdentifier, url); } else { reportCacheExecutor.execute(() -> doSaveMetadata(metadataIdentifier, url)); } } @Override public void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier) { if (syncReport) { doRemoveMetadata(metadataIdentifier); } else { reportCacheExecutor.execute(() -> doRemoveMetadata(metadataIdentifier)); } } @Override public List getExportedURLs(ServiceMetadataIdentifier metadataIdentifier) { // TODO, fallback to local cache return doGetExportedURLs(metadataIdentifier); } @Override public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set urls) { if (syncReport) { doSaveSubscriberData(subscriberMetadataIdentifier, JsonUtils.toJson(urls)); } else { reportCacheExecutor.execute(() -> doSaveSubscriberData(subscriberMetadataIdentifier, JsonUtils.toJson(urls))); } } @Override public List getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) { String content = doGetSubscribedURLs(subscriberMetadataIdentifier); return JsonUtils.toJavaList(content, String.class); } String getProtocol(URL url) { String protocol = url.getSide(); protocol = protocol == null ? url.getProtocol() : protocol; return protocol; } /** * @return if need to continue */ public boolean retry() { return doHandleMetadataCollection(failedReports); } @Override public boolean shouldReportDefinition() { return reportDefinition; } @Override public boolean shouldReportMetadata() { return reportMetadata; } private boolean doHandleMetadataCollection(Map metadataMap) { if (metadataMap.isEmpty()) { return true; } Iterator> iterable = metadataMap.entrySet().iterator(); while (iterable.hasNext()) { Map.Entry item = iterable.next(); if (PROVIDER_SIDE.equals(item.getKey().getSide())) { this.storeProviderMetadata(item.getKey(), (FullServiceDefinition) item.getValue()); } else if (CONSUMER_SIDE.equals(item.getKey().getSide())) { this.storeConsumerMetadata(item.getKey(), (Map) item.getValue()); } } return false; } /** * not private. just for unittest. */ void publishAll() { logger.info(""start to publish all metadata.""); this.doHandleMetadataCollection(allMetadataReports); } /** * between 2:00 am to 6:00 am, the time is random. * * @return */ long calculateStartTime() { Calendar calendar = Calendar.getInstance(); long nowMill = calendar.getTimeInMillis(); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); long [MASK] = calendar.getTimeInMillis() + ONE_DAY_IN_MILLISECONDS - nowMill; return [MASK] + (FOUR_HOURS_IN_MILLISECONDS / 2) + ThreadLocalRandom.current().nextInt(FOUR_HOURS_IN_MILLISECONDS); } class MetadataReportRetry { protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(0, new NamedThreadFactory(""DubboMetadataReportRetryTimer"", true)); volatile ScheduledFuture retryScheduledFuture; final AtomicInteger retryCounter = new AtomicInteger(0); // retry task schedule period long retryPeriod; // if no failed report, wait how many times to run retry task. int retryTimesIfNonFail = 600; int retryLimit; public MetadataReportRetry(int retryTimes, int retryPeriod) { this.retryPeriod = retryPeriod; this.retryLimit = retryTimes; } void startRetryTask() { if (retryScheduledFuture == null) { synchronized (retryCounter) { if (retryScheduledFuture == null) { retryScheduledFuture = retryExecutor.scheduleWithFixedDelay(() -> { // Check and connect to the metadata try { int times = retryCounter.incrementAndGet(); logger.info(""start to retry task for metadata report. retry times:"" + times); if (retry() && times > retryTimesIfNonFail) { cancelRetryTask(); } if (times > retryLimit) { cancelRetryTask(); } } catch (Throwable t) { // Defensive fault tolerance logger.error(COMMON_UNEXPECTED_EXCEPTION, """", """", ""Unexpected error occur at failed retry, cause: "" + t.getMessage(), t); } }, 500, retryPeriod, TimeUnit.MILLISECONDS); } } } } void cancelRetryTask() { if (retryScheduledFuture != null) { retryScheduledFuture.cancel(false); } retryExecutor.shutdown(); } void destroy() { cancelRetryTask(); } /** * @deprecated only for test */ @Deprecated ScheduledExecutorService getRetryExecutor() { return retryExecutor; } } private void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, List urls) { if (CollectionUtils.isEmpty(urls)) { return; } List encodedUrlList = new ArrayList<>(urls.size()); for (String url : urls) { encodedUrlList.add(URL.encode(url)); } doSaveSubscriberData(subscriberMetadataIdentifier, encodedUrlList); } protected abstract void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions); protected abstract void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String serviceParameterString); protected abstract void doSaveMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url); protected abstract void doRemoveMetadata(ServiceMetadataIdentifier metadataIdentifier); protected abstract List doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier); protected abstract void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr); protected abstract String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier); /** * @deprecated only for unit test */ @Deprecated protected ExecutorService getReportCacheExecutor() { return reportCacheExecutor; } /** * @deprecated only for unit test */ @Deprecated protected MetadataReportRetry getMetadataReportRetry() { return metadataReportRetry; } } ","subtract " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.extractor.ts; import static com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS; import static com.google.common.truth.Truth.assertThat; import android.util.SparseArray; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.ParameterizedRobolectricTestRunner; import org.robolectric.ParameterizedRobolectricTestRunner.Parameter; import org.robolectric.ParameterizedRobolectricTestRunner.Parameters; /** Unit test for {@link TsExtractor}. */ @RunWith(ParameterizedRobolectricTestRunner.class) public final class TsExtractorTest { @Parameters(name = ""{0}"") public static ImmutableList params() { return ExtractorAsserts.configs(); } @Parameter public ExtractorAsserts.SimulationConfig simulationConfig; @Test public void sampleWithH262AndMpegAudio() throws Exception { ExtractorAsserts.assertBehavior( TsExtractor::new, ""media/ts/sample_h262_mpeg_audio.ts"", simulationConfig); } @Test public void sampleWithH263() throws Exception { ExtractorAsserts.assertBehavior(TsExtractor::new, ""media/ts/sample_h263.ts"", simulationConfig); } @Test public void sampleWithH264AndMpegAudio() throws Exception { ExtractorAsserts.assertBehavior( TsExtractor::new, ""media/ts/sample_h264_mpeg_audio.ts"", simulationConfig); } @Test public void sampleWithH264NoAccessUnitDelimiters() throws Exception { ExtractorAsserts.assertBehavior( () -> new TsExtractor(FLAG_DETECT_ACCESS_UNITS), ""media/ts/sample_h264_no_access_unit_delimiters.ts"", simulationConfig); } @Test public void sampleWithH264AndDtsAudio() throws Exception { ExtractorAsserts.assertBehavior( () -> new TsExtractor(DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS), ""media/ts/sample_h264_dts_audio.ts"", simulationConfig); } @Test public void sampleWithH265() throws Exception { ExtractorAsserts.assertBehavior(TsExtractor::new, ""media/ts/sample_h265.ts"", simulationConfig); } @Test public void sampleWithH265RpsPred() throws Exception { ExtractorAsserts.assertBehavior( TsExtractor::new, ""media/ts/sample_h265_rps_pred.ts"", simulationConfig); } @Test public void sampleWithScte35() throws Exception { ExtractorAsserts.assertBehavior( TsExtractor::new, ""media/ts/sample_scte35.ts"", new ExtractorAsserts.AssertionConfig.Builder() .setDeduplicateConsecutiveFormats(true) .build(), simulationConfig); } @Test public void sampleWithAit() throws Exception { ExtractorAsserts.assertBehavior( TsExtractor::new, ""media/ts/sample_ait.ts"", new ExtractorAsserts.AssertionConfig.Builder() .setDeduplicateConsecutiveFormats(true) .build(), simulationConfig); } @Test public void sampleWithAc3() throws Exception { ExtractorAsserts.assertBehavior(TsExtractor::new, ""media/ts/sample_ac3.ts"", simulationConfig); } @Test public void sampleWithAc4() throws Exception { ExtractorAsserts.assertBehavior(TsExtractor::new, ""media/ts/sample_ac4.ts"", simulationConfig); } @Test public void sampleWithEac3() throws Exception { ExtractorAsserts.assertBehavior(TsExtractor::new, ""media/ts/sample_eac3.ts"", simulationConfig); } @Test public void sampleWithEac3joc() throws Exception { ExtractorAsserts.assertBehavior( TsExtractor::new, ""media/ts/sample_eac3joc.ts"", simulationConfig); } @Test public void sampleWithLatm() throws Exception { ExtractorAsserts.assertBehavior(TsExtractor::new, ""media/ts/sample_latm.ts"", simulationConfig); } @Test public void streamWithJunkData() throws Exception { ExtractorAsserts.assertBehavior( TsExtractor::new, ""media/ts/sample_with_junk"", simulationConfig); } @Test public void customPesReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false); TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData( TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), ""media/ts/sample_h262_mpeg_audio.ts"")) .setSimulateIOErrors(false) .setSimulateUnknownLength(false) .setSimulatePartialReads(false) .build(); FakeExtractorOutput output = new FakeExtractorOutput(); tsExtractor.init(output); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { readResult = tsExtractor.read(input, seekPositionHolder); if (readResult == Extractor.RESULT_SEEK) { input.setPosition((int) seekPositionHolder.position); } } CustomEsReader reader = factory.esReader; assertThat(reader.packetsRead).isEqualTo(2); TrackOutput trackOutput = reader.getTrackOutput(); assertThat(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */)).isTrue(); assertThat(((FakeTrackOutput) trackOutput).lastFormat) .isEqualTo( new Format.Builder() .setId(""1/257"") .setSampleMimeType(""mime"") .setLanguage(""und"") .build()); } @Test public void customInitialSectionReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true); TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); FakeExtractorInput input = new FakeExtractorInput.Builder() .setData( TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), ""media/ts/sample_with_sdt.ts"")) .setSimulateIOErrors(false) .setSimulateUnknownLength(false) .setSimulatePartialReads(false) .build(); tsExtractor.init(new FakeExtractorOutput()); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { readResult = tsExtractor.read(input, seekPositionHolder); if (readResult == Extractor.RESULT_SEEK) { input.setPosition((int) seekPositionHolder.position); } } assertThat(factory.sdtReader.consumedSdts).isEqualTo(2); } private static final class CustomTsPayloadReaderFactory implements TsPayloadReader.Factory { private final boolean provideSdtReader; private final boolean provideCustomEsReader; private final TsPayloadReader.Factory defaultFactory; private CustomEsReader esReader; private SdtSectionReader sdtReader; public CustomTsPayloadReaderFactory(boolean provideCustomEsReader, boolean provideSdtReader) { this.provideCustomEsReader = provideCustomEsReader; this.provideSdtReader = provideSdtReader; defaultFactory = new DefaultTsPayloadReaderFactory(); } @Override public SparseArray createInitialPayloadReaders() { if (provideSdtReader) { assertThat(sdtReader).isNull(); SparseArray mapping = new SparseArray<>(); sdtReader = new SdtSectionReader(); mapping.put(17, new SectionReader(sdtReader)); return mapping; } else { return defaultFactory.createInitialPayloadReaders(); } } @Override @Nullable public TsPayloadReader createPayloadReader(int [MASK] , EsInfo esInfo) { if (provideCustomEsReader && [MASK] == 3) { esReader = new CustomEsReader(esInfo.language); return new PesReader(esReader); } else { return defaultFactory.createPayloadReader( [MASK] , esInfo); } } } private static final class CustomEsReader implements ElementaryStreamReader { private final String language; private TrackOutput output; public int packetsRead = 0; public CustomEsReader(String language) { this.language = language; } @Override public void seek() {} @Override public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { idGenerator.generateNewId(); output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_UNKNOWN); output.format( new Format.Builder() .setId(idGenerator.getFormatId()) .setSampleMimeType(""mime"") .setLanguage(language) .build()); } @Override public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {} @Override public void consume(ParsableByteArray data) {} @Override public void packetFinished() { packetsRead++; } public TrackOutput getTrackOutput() { return output; } } private static final class SdtSectionReader implements SectionPayloadReader { private int consumedSdts; @Override public void init( TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { // Do nothing. } @Override public void consume(ParsableByteArray sectionData) { // table_id(8), section_syntax_indicator(1), reserved_future_use(1), reserved(2), // section_length(12), transport_stream_id(16), reserved(2), version_number(5), // current_next_indicator(1), section_number(8), last_section_number(8), // original_network_id(16), reserved_future_use(8) sectionData.skipBytes(11); // Start of the service loop. assertThat(sectionData.readUnsignedShort()).isEqualTo(0x5566 /* arbitrary service id */); // reserved_future_use(6), EIT_schedule_flag(1), EIT_present_following_flag(1) sectionData.skipBytes(1); // Assert there is only one service. // Remove running_status(3), free_CA_mode(1) from the descriptors_loop_length with the mask. assertThat(sectionData.readUnsignedShort() & 0xFFF).isEqualTo(sectionData.bytesLeft()); while (sectionData.bytesLeft() > 0) { int descriptorTag = sectionData.readUnsignedByte(); int descriptorLength = sectionData.readUnsignedByte(); if (descriptorTag == 72 /* service descriptor */) { assertThat(sectionData.readUnsignedByte()).isEqualTo(1); // Service type: Digital TV. int serviceProviderNameLength = sectionData.readUnsignedByte(); assertThat(sectionData.readString(serviceProviderNameLength)).isEqualTo(""Some provider""); int serviceNameLength = sectionData.readUnsignedByte(); assertThat(sectionData.readString(serviceNameLength)).isEqualTo(""Some Channel""); } else { sectionData.skipBytes(descriptorLength); } } consumedSdts++; } } } ","streamType " "// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.runtime.commands; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.config.CoreOptions; import com.google.devtools.build.lib.buildtool.BuildRequest; import com.google.devtools.build.lib.buildtool.BuildRequestOptions; import com.google.devtools.build.lib.buildtool.BuildResult; import com.google.devtools.build.lib.buildtool.BuildTool; import com.google.devtools.build.lib.buildtool.InstrumentationFilterSupport; import com.google.devtools.build.lib.buildtool.OutputDirectoryLinksUtils; import com.google.devtools.build.lib.buildtool.PathPrettyPrinter; import com.google.devtools.build.lib.buildtool.buildevent.TestingCompleteEvent; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.exec.ExecutionOptions; import com.google.devtools.build.lib.exec.ExecutionOptions.TestOutputFormat; import com.google.devtools.build.lib.runtime.AggregatingTestListener; import com.google.devtools.build.lib.runtime.BlazeCommand; import com.google.devtools.build.lib.runtime.BlazeCommandResult; import com.google.devtools.build.lib.runtime.BlazeRuntime; import com.google.devtools.build.lib.runtime.Command; import com.google.devtools.build.lib.runtime.CommandEnvironment; import com.google.devtools.build.lib.runtime.TerminalTestResultNotifier; import com.google.devtools.build.lib.runtime.TerminalTestResultNotifier.TestSummaryOptions; import com.google.devtools.build.lib.runtime.TestResultNotifier; import com.google.devtools.build.lib.runtime.TestSummaryPrinter.TestLogPathFormatter; import com.google.devtools.build.lib.runtime.UiOptions; import com.google.devtools.build.lib.server.FailureDetails; import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; import com.google.devtools.build.lib.server.FailureDetails.TestCommand.Code; import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey; import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; import com.google.devtools.build.lib.util.DetailedExitCode; import com.google.devtools.build.lib.util.io.AnsiTerminalPrinter; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.common.options.OptionPriority.PriorityCategory; import com.google.devtools.common.options.OptionsParser; import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.OptionsParsingResult; import java.util.Collection; import java.util.List; /** * Handles the 'test' command on the Blaze command line. */ @Command(name = ""test"", builds = true, inherits = { BuildCommand.class }, options = { TestSummaryOptions.class }, shortDescription = ""Builds and runs the specified test targets."", help = ""resource:test.txt"", completion = ""label-test"", allowResidue = true) public class TestCommand implements BlazeCommand { /** Returns the name of the command to ask the project file for. */ // TODO(hdm): move into BlazeRuntime? It feels odd to duplicate the annotation here. protected String commandName() { return ""test""; } @Override public void editOptions(OptionsParser optionsParser) { TestOutputFormat testOutput = optionsParser.getOptions(ExecutionOptions.class).testOutput; try { if (testOutput == ExecutionOptions.TestOutputFormat.STREAMED) { optionsParser.parse( PriorityCategory.SOFTWARE_REQUIREMENT, ""streamed output requires locally run tests, without sharding"", ImmutableList.of(""--test_sharding_strategy=disabled"", ""--test_strategy=exclusive"")); } } catch (OptionsParsingException e) { throw new IllegalStateException(""Known options failed to parse"", e); } } @Override public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) { TestOutputFormat testOutput = options.getOptions(ExecutionOptions.class).testOutput; if (testOutput == ExecutionOptions.TestOutputFormat.STREAMED) { env.getReporter().handle(Event.warn( ""Streamed test output requested. All tests will be run locally, without sharding, "" + ""one at a time"")); } AnsiTerminalPrinter printer = new AnsiTerminalPrinter( env.getReporter().getOutErr().getOutputStream(), options.getOptions(UiOptions.class).useColor()); // Initialize test handler. AggregatingTestListener testListener = new AggregatingTestListener( options.getOptions(TestSummaryOptions.class), options.getOptions(ExecutionOptions.class), env.getEventBus()); env.getEventBus().register(testListener); return doTest(env, options, testListener, printer); } private BlazeCommandResult doTest( CommandEnvironment env, OptionsParsingResult options, AggregatingTestListener testListener, AnsiTerminalPrinter printer) { BlazeRuntime runtime = env.getRuntime(); // Run simultaneous build and test. List targets; try { targets = TargetPatternsHelper.readFrom(env, options); } catch (TargetPatternsHelper.TargetPatternsHelperException e) { env.getReporter().handle(Event.error(e.getMessage())); return BlazeCommandResult.failureDetail(e.getFailureDetail()); } BuildRequest.Builder builder = BuildRequest.builder() .setCommandName(getClass().getAnnotation(Command.class).name()) .setId(env.getCommandId()) .setOptions(options) .setStartupOptions(runtime.getStartupOptionsProvider()) .setOutErr(env.getReporter().getOutErr()) .setTargets(targets) .setStartTimeMillis(env.getCommandStartTime()) .setRunTests(true); if (options.getOptions(CoreOptions.class).collectCodeCoverage && !options.containsExplicitOption( InstrumentationFilterSupport.INSTRUMENTATION_FILTER_FLAG)) { builder.setNeedsInstrumentationFilter(true); } BuildRequest request = builder.build(); BuildResult buildResult = new BuildTool(env).processRequest(request, null); Collection testTargets = buildResult.getTestTargets(); // TODO(bazel-team): don't handle isEmpty here or fix up a bunch of tests if (buildResult.getSuccessfulTargets() == null) { // This can happen if there were errors in the target parsing or loading phase // (original exitcode=BUILD_FAILURE) or if there weren't but --noanalyze was given // (original exitcode=SUCCESS). String message = ""Couldn't start the build. Unable to run tests""; env.getReporter().handle(Event.error(message)); DetailedExitCode detailedExitCode = buildResult.getSuccess() ? DetailedExitCode.of( FailureDetail.newBuilder() .setMessage(message) .setTestCommand( FailureDetails.TestCommand.newBuilder().setCode(Code.TEST_WITH_NOANALYZE)) .build()) : buildResult.getDetailedExitCode(); env.getEventBus() .post( new TestingCompleteEvent(detailedExitCode.getExitCode(), buildResult.getStopTime())); return BlazeCommandResult.detailedExitCode(detailedExitCode); } // TODO(bazel-team): the check above shadows NO_TESTS_FOUND, but switching the conditions breaks // more tests if (testTargets.isEmpty()) { String message = ""No test targets were found, yet testing was requested""; env.getReporter().handle(Event.error(null, message)); DetailedExitCode detailedExitCode = buildResult.getSuccess() ? DetailedExitCode.of( FailureDetail.newBuilder() .setMessage(message) .setTestCommand( FailureDetails.TestCommand.newBuilder().setCode(Code.NO_TEST_TARGETS)) .build()) : buildResult.getDetailedExitCode(); env.getEventBus() .post(new NoTestsFound(detailedExitCode.getExitCode(), buildResult.getStopTime())); return BlazeCommandResult.detailedExitCode(detailedExitCode); } DetailedExitCode testResults = analyzeTestResults(request, buildResult, testListener, options, env, printer); if (testResults.isSuccess() && !buildResult.getSuccess()) { // If all tests run successfully, test summary should include warning if // there were build errors not associated with the test targets. printer.printLn(AnsiTerminalPrinter.Mode.ERROR + ""All tests passed but there were other errors during the build.\n"" + AnsiTerminalPrinter.Mode.DEFAULT); } DetailedExitCode detailedExitCode = DetailedExitCode.DetailedExitCodeComparator.chooseMoreImportantWithFirstIfTie( buildResult.getDetailedExitCode(), testResults); env.getEventBus() .post(new TestingCompleteEvent(detailedExitCode.getExitCode(), buildResult.getStopTime())); return BlazeCommandResult.detailedExitCode(detailedExitCode); } /** * Analyzes test results and prints summary information. Returns a {@link DetailedExitCode} * summarizing those test results. */ private static DetailedExitCode analyzeTestResults( BuildRequest buildRequest, BuildResult buildResult, AggregatingTestListener listener, OptionsParsingResult options, CommandEnvironment env, AnsiTerminalPrinter printer) { ImmutableSet validatedTargets; if (buildRequest.useValidationAspect()) { validatedTargets = buildResult.getSuccessfulAspects().stream() .filter(key -> BuildRequest.VALIDATION_ASPECT_NAME.equals(key.getAspectName())) .map(AspectKey::getBaseConfiguredTargetKey) .collect(ImmutableSet.toImmutableSet()); } else { validatedTargets = null; } TestResultNotifier notifier = new TerminalTestResultNotifier( printer, makeTestLogPathFormatter(options, env), options); return listener.differentialAnalyzeAndReport( buildResult.getTestTargets(), buildResult.getSkippedTargets(), validatedTargets, notifier); } private static TestLogPathFormatter makeTestLogPathFormatter( OptionsParsingResult options, CommandEnvironment env) { BlazeRuntime runtime = env.getRuntime(); TestSummaryOptions summaryOptions = options.getOptions(TestSummaryOptions.class); if (!summaryOptions.printRelativeTestLogPaths) { return Path::getPathString; } String [MASK] = runtime.getProductName(); BuildRequestOptions requestOptions = env.getOptions().getOptions(BuildRequestOptions.class); // requestOptions.printWorkspaceInOutputPathsIfNeeded is antithetical with // summaryOptions.printRelativeTestLogPaths, so we completely ignore it. PathPrettyPrinter pathPrettyPrinter = OutputDirectoryLinksUtils.getPathPrettyPrinter( runtime.getRuleClassProvider().getSymlinkDefinitions(), requestOptions.getSymlinkPrefix( [MASK] ), [MASK] , env.getWorkspace()); return path -> pathPrettyPrinter.getPrettyPath(path.asFragment()).getPathString(); } } ","productName " "// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android.aapt2; import com.android.SdkConstants; import com.android.builder.core.VariantTypeImpl; import com.android.repository.Revision; import com.android.resources.ResourceFolderType; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.devtools.build.android.AaptCommandBuilder; import com.google.devtools.build.android.AndroidDataSerializer; import com.google.devtools.build.android.DataResourceXml; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.FullyQualifiedName.Factory; import com.google.devtools.build.android.FullyQualifiedName.Qualifiers; import com.google.devtools.build.android.FullyQualifiedName.VirtualType; import com.google.devtools.build.android.ResourceProcessorBusyBox; import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.xml.Namespaces; import com.google.devtools.build.android.xml.ResourcesAttribute; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; /** Invokes aapt2 to compile resources. */ public class ResourceCompiler { /** Types of compiled resources. */ public enum CompiledType { NORMAL(null), GENERATED(""generated""), DEFAULT(""default""); private final String prefix; CompiledType(String prefix) { this.prefix = prefix; } boolean prefixes(String filename) { return prefix != null && filename.startsWith(prefix); } public String asPrefix() { return prefix; } public String asComment() { return prefix; } public String prefix(String path) { return prefix + ""/"" + path; } } public static CompiledType getCompiledType(String fileName) { return Arrays.stream(CompiledType.values()) .filter(t -> t.prefixes(fileName)) .findFirst() .orElse(CompiledType.NORMAL); } static class CompileError extends Aapt2Exception { protected CompileError(Throwable e) { super(e); } private CompileError() { super(); } public static CompileError of(List compilationErrors) { final CompileError compileError = new CompileError(); compilationErrors.forEach(compileError::addSuppressed); return compileError; } } private static final Logger logger = Logger.getLogger(ResourceCompiler.class.getName()); // https://android-review.googlesource.com/c/platform/frameworks/base/+/1202901 public static final boolean USE_VISIBILITY_FROM_AAPT2 = ResourceProcessorBusyBox.getProperty(""use_visibility_from_aapt2""); private final CompilingVisitor compilingVisitor; private static class CompileTask implements Callable> { private final Path file; private final Path compiledResourcesOut; private final Path aapt2; private final Revision buildToolsVersion; private final Optional generatedResourcesOut; private CompileTask( Path file, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion, Optional generatedResourcesOut) { this.file = file; this.compiledResourcesOut = compiledResourcesOut; this.aapt2 = aapt2; this.buildToolsVersion = buildToolsVersion; this.generatedResourcesOut = generatedResourcesOut; } @Override public List call() throws Exception { final String directoryName = file.getParent().getFileName().toString(); final Qualifiers qualifiers = Qualifiers.parseFrom(directoryName); final ResourceFolderType resourceFolderType = qualifiers.asFolderType(); if (resourceFolderType == null) { throw new CompileError( new IllegalArgumentException(""Unexpected resource folder for file: "" + file)); } final String filename = interpolateAapt2Filename(resourceFolderType, file); final List results = new ArrayList<>(); if (resourceFolderType.equals(ResourceFolderType.VALUES) || (resourceFolderType.equals(ResourceFolderType.RAW) && file.getFileName().toString().endsWith("".xml""))) { extractAttributes(directoryName, filename, results); } if (qualifiers.containDefaultLocale() && resourceFolderType.equals(ResourceFolderType.VALUES)) { compile( directoryName, filename, results, compiledResourcesOut.resolve(CompiledType.DEFAULT.asPrefix()), file, false); // aapt2 only generates pseudo locales for the default locale. // TODO(b/149251235): omit this file if the output is identical to the default config above. generatedResourcesOut.ifPresent( out -> compile(directoryName, filename, results, out, file, true)); } else { compile(directoryName, filename, results, compiledResourcesOut, file, false); } return results; } static String interpolateAapt2Filename(ResourceFolderType resourceFolderType, Path file) { // res//foo.bar -> foo.bar String filename = file.getFileName().toString(); if (!resourceFolderType.equals(ResourceFolderType.VALUES)) { return filename; } int periodIndex = filename.indexOf('.'); // res/values/foo -> foo.arsc if (periodIndex == -1) { return filename + "".arsc""; } // res/values/foo.bar.baz -> throw error. if (filename.lastIndexOf('.') != periodIndex) { throw new CompileError( new IllegalArgumentException( ""aapt2 does not support compiling resource xmls with multiple periods in the "" + ""filename: "" + file)); } // res/values/foo.xml -> foo.arsc return filename.substring(0, periodIndex) + "".arsc""; } private void compile( String type, String filename, List results, Path compileOutRoot, Path file, boolean generatePseudoLocale) { try { Path destination = CompilingVisitor.destinationPath(file, compileOutRoot); final Path compiledResourcePath = destination.resolve(type + ""_"" + filename + "".flat""); logger.fine( new AaptCommandBuilder(aapt2) .forBuildToolsVersion(buildToolsVersion) .forVariantType(VariantTypeImpl.LIBRARY) .add(""compile"") .add(""-v"") .add(""--legacy"") .when(USE_VISIBILITY_FROM_AAPT2) .thenAdd(""--preserve-visibility-of-styleables"") .when(generatePseudoLocale) .thenAdd(""--pseudo-localize"") .add(""-o"", destination.toString()) .add(file.toString()) .execute(""Compiling "" + file)); Preconditions.checkArgument( Files.exists(compiledResourcePath), ""%s does not exist after aapt2 ran."", compiledResourcePath); results.add(compiledResourcePath); } catch (IOException e) { throw new CompileError(e); } } private void extractAttributes(String type, String filename, List results) throws Exception { XMLEventReader xmlEventReader = null; try { // aapt2 compile strips out namespaces and attributes from the resources tag. // Read them here separately and package them with the other flat files. xmlEventReader = XMLInputFactory.newInstance() .createXMLEventReader(new FileInputStream(file.toString())); // Iterate through the XML until we find a start element. // This should mimic xmlEventReader.nextTag() except that it also skips DTD elements. StartElement rootElement = null; while (xmlEventReader.hasNext()) { XMLEvent event = xmlEventReader.nextEvent(); if (event.getEventType() != XMLStreamConstants.COMMENT && event.getEventType() != XMLStreamConstants.DTD && event.getEventType() != XMLStreamConstants.PROCESSING_INSTRUCTION && event.getEventType() != XMLStreamConstants.SPACE && event.getEventType() != XMLStreamConstants.START_DOCUMENT) { // If the event should not be skipped, try parsing it as a start element here. // If the event is not a start element, an appropriate exception will be thrown. rootElement = event.asStartElement(); break; } } if (rootElement == null) { throw new Exception(""No start element found in resource XML file: "" + file.toString()); } Iterator attributeIterator = XmlResourceValues.iterateAttributesFrom(rootElement); if (attributeIterator.hasNext()) { results.add(createAttributesProto(type, filename, attributeIterator)); } } finally { if (xmlEventReader != null) { xmlEventReader.close(); } } } private Path createAttributesProto( String type, String filename, Iterator attributeIterator) throws IOException { AndroidDataSerializer serializer = AndroidDataSerializer.create(); final Path resourcesAttributesPath = CompilingVisitor.destinationPath(file, compiledResourcesOut) .resolve(type + ""_"" + filename + CompiledResources.ATTRIBUTES_FILE_EXTENSION); Preconditions.checkArgument( !Files.exists(resourcesAttributesPath), ""%s was already created for another resource."", resourcesAttributesPath); while (attributeIterator.hasNext()) { Attribute attribute = attributeIterator.next(); String namespaceUri = attribute.getName().getNamespaceURI(); String localPart = attribute.getName().getLocalPart(); String prefix = attribute.getName().getPrefix(); QName qName = new QName(namespaceUri, localPart, prefix); Namespaces namespaces = Namespaces.from(qName); String attributeName = namespaceUri.isEmpty() ? localPart : prefix + "":"" + localPart; final String[] dirNameAndQualifiers = type.split(SdkConstants.RES_QUALIFIER_SEP); Factory [MASK] = Factory.fromDirectoryName(dirNameAndQualifiers); FullyQualifiedName fqn = [MASK] .create(VirtualType.RESOURCES_ATTRIBUTE, qName.toString()); ResourcesAttribute resourceAttribute = ResourcesAttribute.of(fqn, attributeName, attribute.getValue()); DataResourceXml resource = DataResourceXml.createWithNamespaces(file, resourceAttribute, namespaces); serializer.queueForSerialization(fqn, resource); } serializer.flushTo(resourcesAttributesPath); return resourcesAttributesPath; } @Override public String toString() { return ""ResourceCompiler.CompileTask("" + file + "")""; } } private static class CompilingVisitor extends SimpleFileVisitor { private final ListeningExecutorService executorService; private final Path compiledResourcesOut; private final Set pathToProcessed = new LinkedHashSet<>(); private final Path aapt2; private final Revision buildToolsVersion; private final Optional generatedResourcesOut; public CompilingVisitor( ListeningExecutorService executorService, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion, Optional generatedResourcesOut) { this.executorService = executorService; this.compiledResourcesOut = compiledResourcesOut; this.aapt2 = aapt2; this.buildToolsVersion = buildToolsVersion; this.generatedResourcesOut = generatedResourcesOut; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { // Ignore directories and ""hidden"" files that start with ., ends with .tmp or .params files. final String fileName = file.getFileName().toString(); if (!Files.isDirectory(file) && !fileName.startsWith(""."") && !fileName.endsWith("".tmp"") && !fileName.endsWith("".params"")) { pathToProcessed.add(file); } return super.visitFile(file, attrs); } public static Path destinationPath(Path file, Path compiledResourcesOut) { // Creates a relative output path based on the input path under the // compiledResources path. try { return Files.createDirectories( compiledResourcesOut.resolve( (file.isAbsolute() ? file.getRoot().relativize(file) : file) .getParent() .getParent())); } catch (IOException e) { throw new CompileError(e); } } List getCompiledArtifacts() { generatedResourcesOut.ifPresent( out -> { try { Files.createDirectories(out); } catch (IOException e) { throw new CompileError(e); } }); List>> tasks = new ArrayList<>(); for (Path uncompiled : pathToProcessed) { tasks.add( executorService.submit( new CompileTask( uncompiled, compiledResourcesOut, aapt2, buildToolsVersion, generatedResourcesOut))); } ImmutableList.Builder compiled = ImmutableList.builder(); ImmutableList.Builder generated = ImmutableList.builder(); List compilationErrors = new ArrayList<>(); for (ListenableFuture> task : tasks) { try { // Split the generated and non-generated resources into different collections. // This allows the generated files to be placed first in the compile order, // ensuring that the generated locale (en-XA and ar-XB) can be overwritten by // user provided versions for those locales, as aapt2 will take the last value for // a configuration when linking. task.get() .forEach( path -> { if (generatedResourcesOut.map(path::startsWith).orElse(false)) { generated.add(path); } else { compiled.add(path); } }); } catch (InterruptedException | ExecutionException e) { compilationErrors.add(e.getCause() != null ? e.getCause() : e); } } generated.addAll(compiled.build()); if (compilationErrors.isEmpty()) { // ensure that the generated files are before the normal files. return ImmutableList.sortedCopyOf(generated.build()); } throw CompileError.of(compilationErrors); } } /** Creates a new {@link ResourceCompiler}. */ public static ResourceCompiler create( ListeningExecutorService executorService, Path compiledResources, Path aapt2, Revision buildToolsVersion, boolean generatePseudoLocale) { return new ResourceCompiler( new CompilingVisitor( executorService, compiledResources, aapt2, buildToolsVersion, generatePseudoLocale ? Optional.of(compiledResources.resolve(CompiledType.GENERATED.asPrefix())) : Optional.empty())); } private ResourceCompiler(CompilingVisitor compilingVisitor) { this.compilingVisitor = compilingVisitor; } /** Adds a task to compile the directory using aapt2. */ public void queueDirectoryForCompilation(Path resource) throws IOException { Files.walkFileTree(resource, compilingVisitor); } /** Returns all paths of the aapt2 compiled resources. */ public List getCompiledArtifacts() throws InterruptedException, ExecutionException { return compilingVisitor.getCompiledArtifacts(); } } ","fqnFactory " "/* * Copyright 2016 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.ssl; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.ssl.util.LazyJavaxX509Certificate; import io.netty.handler.ssl.util.LazyX509Certificate; import io.netty.internal.tcnative.AsyncTask; import io.netty.internal.tcnative.Buffer; import io.netty.internal.tcnative.SSL; import io.netty.util.AbstractReferenceCounted; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCounted; import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetectorFactory; import io.netty.util.ResourceLeakTracker; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.SuppressJava6Requirement; import io.netty.util.internal.ThrowableUtil; import io.netty.util.internal.UnstableApi; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.security.Principal; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; import javax.security.cert.X509Certificate; import static io.netty.handler.ssl.OpenSsl.memoryAddress; import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH; import static io.netty.util.internal.EmptyArrays.EMPTY_STRINGS; import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkNotNullArrayParam; import static io.netty.util.internal.ObjectUtil.checkNotNullWithIAE; import static java.lang.Integer.MAX_VALUE; import static java.lang.Math.max; import static java.lang.Math.min; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_TASK; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW; import static javax.net.ssl.SSLEngineResult.Status.BUFFER_UNDERFLOW; import static javax.net.ssl.SSLEngineResult.Status.CLOSED; import static javax.net.ssl.SSLEngineResult.Status.OK; /** * Implements a {@link SSLEngine} using * OpenSSL BIO abstractions. *

Instances of this class must be {@link #release() released} or else native memory will leak! * *

Instances of this class must be released before the {@link ReferenceCountedOpenSslContext} * the instance depends upon are released. Otherwise if any method of this class is called which uses the * the {@link ReferenceCountedOpenSslContext} JNI resources the JVM may crash. */ public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted, ApplicationProtocolAccessor { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslEngine.class); private static final ResourceLeakDetector leakDetector = ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslEngine.class); private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2 = 0; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3 = 1; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1 = 2; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1 = 3; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2 = 4; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3 = 5; private static final int[] OPENSSL_OP_NO_PROTOCOLS = { SSL.SSL_OP_NO_SSLv2, SSL.SSL_OP_NO_SSLv3, SSL.SSL_OP_NO_TLSv1, SSL.SSL_OP_NO_TLSv1_1, SSL.SSL_OP_NO_TLSv1_2, SSL.SSL_OP_NO_TLSv1_3 }; /** * Depends upon tcnative ... only use if tcnative is available! */ static final int MAX_PLAINTEXT_LENGTH = SSL.SSL_MAX_PLAINTEXT_LENGTH; /** * Depends upon tcnative ... only use if tcnative is available! */ static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH; private static final SSLEngineResult NEED_UNWRAP_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0); private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0); private static final SSLEngineResult NEED_WRAP_OK = new SSLEngineResult(OK, NEED_WRAP, 0, 0); private static final SSLEngineResult NEED_WRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_WRAP, 0, 0); private static final SSLEngineResult CLOSED_NOT_HANDSHAKING = new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0); // OpenSSL state private long ssl; private long networkBIO; private enum HandshakeState { /** * Not started yet. */ NOT_STARTED, /** * Started via unwrap/wrap. */ STARTED_IMPLICITLY, /** * Started via {@link #beginHandshake()}. */ STARTED_EXPLICITLY, /** * Handshake is finished. */ FINISHED } private HandshakeState handshakeState = HandshakeState.NOT_STARTED; private boolean receivedShutdown; private volatile boolean destroyed; private volatile String applicationProtocol; private volatile boolean needTask; private String[] explicitlyEnabledProtocols; private boolean sessionSet; // Reference Counting private final ResourceLeakTracker leak; private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() { @Override public ReferenceCounted touch(Object hint) { if (leak != null) { leak.record(hint); } return ReferenceCountedOpenSslEngine.this; } @Override protected void deallocate() { shutdown(); if (leak != null) { boolean closed = leak.close(ReferenceCountedOpenSslEngine.this); assert closed; } parentContext.release(); } }; private volatile ClientAuth clientAuth = ClientAuth.NONE; // Updated once a new handshake is started and so the SSLSession reused. private volatile long lastAccessed = -1; private String endPointIdentificationAlgorithm; // Store as object as AlgorithmConstraints only exists since java 7. private Object algorithmConstraints; private List sniHostNames; // Mark as volatile as accessed by checkSniHostnameMatch(...) and also not specify the SNIMatcher type to allow us // using it with java7. private volatile Collection matchers; // SSL Engine status variables private boolean isInboundDone; private boolean outboundClosed; final boolean jdkCompatibilityMode; private final boolean clientMode; final ByteBufAllocator alloc; private final OpenSslEngineMap engineMap; private final OpenSslApplicationProtocolNegotiator apn; private final ReferenceCountedOpenSslContext parentContext; private final OpenSslSession session; private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1]; private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1]; private final boolean enableOcsp; private int maxWrapOverhead; private int maxWrapBufferSize; private Throwable pendingException; /** * Create a new instance. * @param context Reference count release responsibility is not transferred! The callee still owns this object. * @param alloc The allocator to use. * @param peerHost The peer host name. * @param peerPort The peer port. * @param jdkCompatibilityMode {@code true} to behave like described in * https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html. * {@code false} allows for partial and/or multiple packets to be process in a single * wrap or unwrap call. * @param leakDetection {@code true} to enable leak detection of this object. */ ReferenceCountedOpenSslEngine(ReferenceCountedOpenSslContext context, final ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode, boolean leakDetection) { super(peerHost, peerPort); OpenSsl.ensureAvailability(); engineMap = context.engineMap; enableOcsp = context.enableOcsp; this.jdkCompatibilityMode = jdkCompatibilityMode; this.alloc = checkNotNull(alloc, ""alloc""); apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator(); clientMode = context.isClient(); if (PlatformDependent.javaVersion() >= 7) { session = new ExtendedOpenSslSession(new DefaultOpenSslSession(context.sessionContext())) { private String[] peerSupportedSignatureAlgorithms; private List requestedServerNames; @Override public List getRequestedServerNames() { if (clientMode) { return Java8SslUtils.getSniHostNames(sniHostNames); } else { synchronized (ReferenceCountedOpenSslEngine.this) { if (requestedServerNames == null) { if (isDestroyed()) { requestedServerNames = Collections.emptyList(); } else { String name = SSL.getSniHostname(ssl); if (name == null) { requestedServerNames = Collections.emptyList(); } else { // Convert to bytes as we do not want to do any strict validation of the // SNIHostName while creating it. requestedServerNames = Java8SslUtils.getSniHostName( SSL.getSniHostname(ssl).getBytes(CharsetUtil.UTF_8)); } } } return requestedServerNames; } } } @Override public String[] getPeerSupportedSignatureAlgorithms() { synchronized (ReferenceCountedOpenSslEngine.this) { if (peerSupportedSignatureAlgorithms == null) { if (isDestroyed()) { peerSupportedSignatureAlgorithms = EMPTY_STRINGS; } else { String[] algs = SSL.getSigAlgs(ssl); if (algs == null) { peerSupportedSignatureAlgorithms = EMPTY_STRINGS; } else { Set algorithmList = new LinkedHashSet(algs.length); for (String alg: algs) { String converted = SignatureAlgorithmConverter.toJavaName(alg); if (converted != null) { algorithmList.add(converted); } } peerSupportedSignatureAlgorithms = algorithmList.toArray(EMPTY_STRINGS); } } } return peerSupportedSignatureAlgorithms.clone(); } } @Override public List getStatusResponses() { byte[] ocspResponse = null; if (enableOcsp && clientMode) { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { ocspResponse = SSL.getOcspResponse(ssl); } } } return ocspResponse == null ? Collections.emptyList() : Collections.singletonList(ocspResponse); } }; } else { session = new DefaultOpenSslSession(context.sessionContext()); } if (!context.sessionContext().useKeyManager()) { session.setLocalCertificate(context.keyCertChain); } Lock readerLock = context.ctxLock.readLock(); readerLock.lock(); final long finalSsl; try { finalSsl = SSL.newSSL(context.ctx, !context.isClient()); } finally { readerLock.unlock(); } synchronized (this) { ssl = finalSsl; try { networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize()); // Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the // needed JNI methods. setClientAuth(clientMode ? ClientAuth.NONE : context.clientAuth); if (context.protocols != null) { setEnabledProtocols0(context.protocols, true); } else { this.explicitlyEnabledProtocols = getEnabledProtocols(); } // Use SNI if peerHost was specified and a valid hostname // See https://github.com/netty/netty/issues/4746 if (clientMode && SslUtils.isValidHostNameForSNI(peerHost)) { // If on java8 and later we should do some extra validation to ensure we can construct the // SNIHostName later again. if (PlatformDependent.javaVersion() >= 8) { if (Java8SslUtils.isValidHostNameForSNI(peerHost)) { SSL.setTlsExtHostName(ssl, peerHost); sniHostNames = Collections.singletonList(peerHost); } } else { SSL.setTlsExtHostName(ssl, peerHost); sniHostNames = Collections.singletonList(peerHost); } } if (enableOcsp) { SSL.enableOcsp(ssl); } if (!jdkCompatibilityMode) { SSL.setMode(ssl, SSL.getMode(ssl) | SSL.SSL_MODE_ENABLE_PARTIAL_WRITE); } if (isProtocolEnabled(SSL.getOptions(ssl), SSL.SSL_OP_NO_TLSv1_3, SslProtocols.TLS_v1_3)) { final boolean enableTickets = clientMode ? ReferenceCountedOpenSslContext.CLIENT_ENABLE_SESSION_TICKET_TLSV13 : ReferenceCountedOpenSslContext.SERVER_ENABLE_SESSION_TICKET_TLSV13; if (enableTickets) { // We should enable session tickets for stateless resumption when TLSv1.3 is enabled. This // is also done by OpenJDK and without this session resumption does not work at all with // BoringSSL when TLSv1.3 is used as BoringSSL only supports stateless resumption with TLSv1.3: // // See: // - https://bugs.openjdk.java.net/browse/JDK-8223922 // - https://boringssl.googlesource.com/boringssl/+/refs/heads/master/ssl/tls13_server.cc#104 SSL.clearOptions(ssl, SSL.SSL_OP_NO_TICKET); } } if (OpenSsl.isBoringSSL() && clientMode) { // If in client-mode and BoringSSL let's allow to renegotiate once as the server may use this // for client auth. // // See https://github.com/netty/netty/issues/11529 SSL.setRenegotiateMode(ssl, SSL.SSL_RENEGOTIATE_ONCE); } // setMode may impact the overhead. calculateMaxWrapOverhead(); } catch (Throwable cause) { // Call shutdown so we are sure we correctly release all native memory and also guard against the // case when shutdown() will be called by the finalizer again. shutdown(); PlatformDependent.throwException(cause); } } // Now that everything looks good and we're going to successfully return the // object so we need to retain a reference to the parent context. parentContext = context; parentContext.retain(); // Only create the leak after everything else was executed and so ensure we don't produce a false-positive for // the ResourceLeakDetector. leak = leakDetection ? leakDetector.track(this) : null; } final synchronized String[] authMethods() { if (isDestroyed()) { return EMPTY_STRINGS; } return SSL.authenticationMethods(ssl); } final boolean setKeyMaterial(OpenSslKeyMaterial keyMaterial) throws Exception { synchronized (this) { if (isDestroyed()) { return false; } SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress()); } session.setLocalCertificate(keyMaterial.certificateChain()); return true; } final synchronized SecretKeySpec masterKey() { if (isDestroyed()) { return null; } return new SecretKeySpec(SSL.getMasterKey(ssl), ""AES""); } synchronized boolean isSessionReused() { if (isDestroyed()) { return false; } return SSL.isSessionReused(ssl); } /** * Sets the OCSP response. */ @UnstableApi public void setOcspResponse(byte[] response) { if (!enableOcsp) { throw new IllegalStateException(""OCSP stapling is not enabled""); } if (clientMode) { throw new IllegalStateException(""Not a server SSLEngine""); } synchronized (this) { if (!isDestroyed()) { SSL.setOcspResponse(ssl, response); } } } /** * Returns the OCSP response or {@code null} if the server didn't provide a stapled OCSP response. */ @UnstableApi public byte[] getOcspResponse() { if (!enableOcsp) { throw new IllegalStateException(""OCSP stapling is not enabled""); } if (!clientMode) { throw new IllegalStateException(""Not a client SSLEngine""); } synchronized (this) { if (isDestroyed()) { return EmptyArrays.EMPTY_BYTES; } return SSL.getOcspResponse(ssl); } } @Override public final int refCnt() { return refCnt.refCnt(); } @Override public final ReferenceCounted retain() { refCnt.retain(); return this; } @Override public final ReferenceCounted retain(int increment) { refCnt.retain(increment); return this; } @Override public final ReferenceCounted touch() { refCnt.touch(); return this; } @Override public final ReferenceCounted touch(Object hint) { refCnt.touch(hint); return this; } @Override public final boolean release() { return refCnt.release(); } @Override public final boolean release(int decrement) { return refCnt.release(decrement); } // These method will override the method defined by Java 8u251 and later. As we may compile with an earlier // java8 version we don't use @Override annotations here. public String getApplicationProtocol() { return applicationProtocol; } // These method will override the method defined by Java 8u251 and later. As we may compile with an earlier // java8 version we don't use @Override annotations here. public String getHandshakeApplicationProtocol() { return applicationProtocol; } @Override public final synchronized SSLSession getHandshakeSession() { // Javadocs state return value should be: // null if this instance is not currently handshaking, or if the current handshake has not // progressed far enough to create a basic SSLSession. Otherwise, this method returns the // SSLSession currently being negotiated. switch(handshakeState) { case NOT_STARTED: case FINISHED: return null; default: return session; } } /** * Returns the pointer to the {@code SSL} object for this {@link ReferenceCountedOpenSslEngine}. * Be aware that it is freed as soon as the {@link #release()} or {@link #shutdown()} methods are called. * At this point {@code 0} will be returned. */ public final synchronized long sslPointer() { return ssl; } /** * Destroys this engine. */ public final synchronized void shutdown() { if (!destroyed) { destroyed = true; // Let's check if engineMap is null as it could be in theory if we throw an OOME during the construction of // ReferenceCountedOpenSslEngine (before we assign the field). This is needed as shutdown() is called from // the finalizer as well. if (engineMap != null) { engineMap.remove(ssl); } SSL.freeSSL(ssl); ssl = networkBIO = 0; isInboundDone = outboundClosed = true; } // On shutdown clear all errors SSL.clearError(); } /** * Write plaintext data to the OpenSSL internal BIO * * Calling this function with src.remaining == 0 is undefined. */ private int writePlaintextData(final ByteBuffer src, int len) { final int pos = src.position(); final int limit = src.limit(); final int sslWrote; if (src.isDirect()) { sslWrote = SSL.writeToSSL(ssl, bufferAddress(src) + pos, len); if (sslWrote > 0) { src.position(pos + sslWrote); } } else { ByteBuf buf = alloc.directBuffer(len); try { src.limit(pos + len); buf.setBytes(0, src); src.limit(limit); sslWrote = SSL.writeToSSL(ssl, memoryAddress(buf), len); if (sslWrote > 0) { src.position(pos + sslWrote); } else { src.position(pos); } } finally { buf.release(); } } return sslWrote; } synchronized void bioSetFd(int fd) { if (!isDestroyed()) { SSL.bioSetFd(this.ssl, fd); } } /** * Write encrypted data to the OpenSSL network BIO. */ private ByteBuf writeEncryptedData(final ByteBuffer src, int len) throws SSLException { final int pos = src.position(); if (src.isDirect()) { SSL.bioSetByteBuffer(networkBIO, bufferAddress(src) + pos, len, false); } else { final ByteBuf buf = alloc.directBuffer(len); try { final int limit = src.limit(); src.limit(pos + len); buf.writeBytes(src); // Restore the original position and limit because we don't want to consume from `src`. src.position(pos); src.limit(limit); SSL.bioSetByteBuffer(networkBIO, memoryAddress(buf), len, false); return buf; } catch (Throwable cause) { buf.release(); PlatformDependent.throwException(cause); } } return null; } /** * Read plaintext data from the OpenSSL internal BIO */ private int readPlaintextData(final ByteBuffer dst) throws SSLException { final int sslRead; final int pos = dst.position(); if (dst.isDirect()) { sslRead = SSL.readFromSSL(ssl, bufferAddress(dst) + pos, dst.limit() - pos); if (sslRead > 0) { dst.position(pos + sslRead); } } else { final int limit = dst.limit(); final int len = min(maxEncryptedPacketLength0(), limit - pos); final ByteBuf buf = alloc.directBuffer(len); try { sslRead = SSL.readFromSSL(ssl, memoryAddress(buf), len); if (sslRead > 0) { dst.limit(pos + sslRead); buf.getBytes(buf.readerIndex(), dst); dst.limit(limit); } } finally { buf.release(); } } return sslRead; } /** * Visible only for testing! */ final synchronized int maxWrapOverhead() { return maxWrapOverhead; } /** * Visible only for testing! */ final synchronized int maxEncryptedPacketLength() { return maxEncryptedPacketLength0(); } /** * This method is intentionally not synchronized, only use if you know you are in the EventLoop * thread and visibility on {@link #maxWrapOverhead} is achieved via other synchronized blocks. */ final int maxEncryptedPacketLength0() { return maxWrapOverhead + MAX_PLAINTEXT_LENGTH; } /** * This method is intentionally not synchronized, only use if you know you are in the EventLoop * thread and visibility on {@link #maxWrapBufferSize} and {@link #maxWrapOverhead} is achieved * via other synchronized blocks. *
* Calculates the max size of a single wrap operation for the given plaintextLength and * numComponents. */ final int calculateMaxLengthForWrap(int plaintextLength, int numComponents) { return (int) min(maxWrapBufferSize, plaintextLength + (long) maxWrapOverhead * numComponents); } /** * This method is intentionally not synchronized, only use if you know you are in the EventLoop * thread and visibility on {@link #maxWrapOverhead} is achieved via other synchronized blocks. *
* Calculates the size of the out net buf to create for the given plaintextLength and numComponents. * This is not related to the max size per wrap, as we can wrap chunks at a time into one out net buf. */ final int calculateOutNetBufSize(int plaintextLength, int numComponents) { return (int) min(MAX_VALUE, plaintextLength + (long) maxWrapOverhead * numComponents); } final synchronized int sslPending() { return sslPending0(); } /** * It is assumed this method is called in a synchronized block (or the constructor)! */ private void calculateMaxWrapOverhead() { maxWrapOverhead = SSL.getMaxWrapOverhead(ssl); // maxWrapBufferSize must be set after maxWrapOverhead because there is a dependency on this value. // If jdkCompatibility mode is off we allow enough space to encrypt 16 buffers at a time. This could be // configurable in the future if necessary. maxWrapBufferSize = jdkCompatibilityMode ? maxEncryptedPacketLength0() : maxEncryptedPacketLength0() << 4; } private int sslPending0() { // OpenSSL has a limitation where if you call SSL_pending before the handshake is complete OpenSSL will throw a // ""called a function you should not call"" error. Using the TLS_method instead of SSLv23_method may solve this // issue but this API is only available in 1.1.0+ [1]. // [1] https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_new.html return handshakeState != HandshakeState.FINISHED ? 0 : SSL.sslPending(ssl); } private boolean isBytesAvailableEnoughForWrap(int bytesAvailable, int plaintextLength, int numComponents) { return bytesAvailable - (long) maxWrapOverhead * numComponents >= plaintextLength; } @Override public final SSLEngineResult wrap( final ByteBuffer[] srcs, int offset, final int length, final ByteBuffer dst) throws SSLException { // Throw required runtime exceptions checkNotNullWithIAE(srcs, ""srcs""); checkNotNullWithIAE(dst, ""dst""); if (offset >= srcs.length || offset + length > srcs.length) { throw new IndexOutOfBoundsException( ""offset: "" + offset + "", length: "" + length + "" (expected: offset <= offset + length <= srcs.length ("" + srcs.length + ""))""); } if (dst.isReadOnly()) { throw new ReadOnlyBufferException(); } synchronized (this) { if (isOutboundDone()) { // All drained in the outbound buffer return isInboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_UNWRAP_CLOSED; } int bytesProduced = 0; ByteBuf bioReadCopyBuf = null; try { // Setup the BIO buffer so that we directly write the encryption results into dst. if (dst.isDirect()) { SSL.bioSetByteBuffer(networkBIO, bufferAddress(dst) + dst.position(), dst.remaining(), true); } else { bioReadCopyBuf = alloc.directBuffer(dst.remaining()); SSL.bioSetByteBuffer(networkBIO, memoryAddress(bioReadCopyBuf), bioReadCopyBuf.writableBytes(), true); } int bioLengthBefore = SSL.bioLengthByteBuffer(networkBIO); // Explicitly use outboundClosed as we want to drain any bytes that are still present. if (outboundClosed) { // If the outbound was closed we want to ensure we can produce the alert to the destination buffer. // This is true even if we not using jdkCompatibilityMode. // // We use a plaintextLength of 2 as we at least want to have an alert fit into it. // https://tools.ietf.org/html/rfc5246#section-7.2 if (!isBytesAvailableEnoughForWrap(dst.remaining(), 2, 1)) { return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); } // There is something left to drain. // See https://github.com/netty/netty/issues/6260 bytesProduced = SSL.bioFlushByteBuffer(networkBIO); if (bytesProduced <= 0) { return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, 0); } // It is possible when the outbound was closed there was not enough room in the non-application // buffers to hold the close_notify. We should keep trying to close until we consume all the data // OpenSSL can give us. if (!doSSLShutdown()) { return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, bytesProduced); } bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); return newResultMayFinishHandshake(NEED_WRAP, 0, bytesProduced); } // Flush any data that may be implicitly generated by OpenSSL (handshake, close, etc..). SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; HandshakeState oldHandshakeState = handshakeState; // Prepare OpenSSL to work in server mode and receive handshake if (handshakeState != HandshakeState.FINISHED) { if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { // Update accepted so we know we triggered the handshake via wrap handshakeState = HandshakeState.STARTED_IMPLICITLY; } // Flush any data that may have been written implicitly during the handshake by OpenSSL. bytesProduced = SSL.bioFlushByteBuffer(networkBIO); if (pendingException != null) { // TODO(scott): It is possible that when the handshake failed there was not enough room in the // non-application buffers to hold the alert. We should get all the data before progressing on. // However I'm not aware of a way to do this with the OpenSSL APIs. // See https://github.com/netty/netty/issues/6385. // We produced / consumed some data during the handshake, signal back to the caller. // If there is a handshake exception and we have produced data, we should send the data before // we allow handshake() to throw the handshake exception. // // When the user calls wrap() again we will propagate the handshake error back to the user as // soon as there is no more data to was produced (as part of an alert etc). if (bytesProduced > 0) { return newResult(NEED_WRAP, 0, bytesProduced); } // Nothing was produced see if there is a handshakeException that needs to be propagated // to the caller by calling handshakeException() which will return the right HandshakeStatus // if it can ""recover"" from the exception for now. return newResult(handshakeException(), 0, 0); } status = handshake(); // Handshake may have generated more data, for example if the internal SSL buffer is small // we may have freed up space by flushing above. bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); if (status == NEED_TASK) { return newResult(status, 0, bytesProduced); } if (bytesProduced > 0) { // If we have filled up the dst buffer and we have not finished the handshake we should try to // wrap again. Otherwise we should only try to wrap again if there is still data pending in // SSL buffers. return newResult(mayFinishHandshake(status != FINISHED ? bytesProduced == bioLengthBefore ? NEED_WRAP : getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED), 0, bytesProduced); } if (status == NEED_UNWRAP) { // Signal if the outbound is done or not. return isOutboundDone() ? NEED_UNWRAP_CLOSED : NEED_UNWRAP_OK; } // Explicit use outboundClosed and not outboundClosed() as we want to drain any bytes that are // still present. if (outboundClosed) { bytesProduced = SSL.bioFlushByteBuffer(networkBIO); return newResultMayFinishHandshake(status, 0, bytesProduced); } } final int endOffset = offset + length; if (jdkCompatibilityMode || // If the handshake was not finished before we entered the method, we also ensure we only // wrap one record. We do this to ensure we not produce any extra data before the caller // of the method is able to observe handshake completion and react on it. oldHandshakeState != HandshakeState.FINISHED) { int srcsLen = 0; for (int i = offset; i < endOffset; ++i) { final ByteBuffer src = srcs[i]; if (src == null) { throw new IllegalArgumentException(""srcs["" + i + ""] is null""); } if (srcsLen == MAX_PLAINTEXT_LENGTH) { continue; } srcsLen += src.remaining(); if (srcsLen > MAX_PLAINTEXT_LENGTH || srcsLen < 0) { // If srcLen > MAX_PLAINTEXT_LENGTH or secLen < 0 just set it to MAX_PLAINTEXT_LENGTH. // This also help us to guard against overflow. // We not break out here as we still need to check for null entries in srcs[]. srcsLen = MAX_PLAINTEXT_LENGTH; } } // jdkCompatibilityMode will only produce a single TLS packet, and we don't aggregate src buffers, // so we always fix the number of buffers to 1 when checking if the dst buffer is large enough. if (!isBytesAvailableEnoughForWrap(dst.remaining(), srcsLen, 1)) { return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); } } // There was no pending data in the network BIO -- encrypt any application data int bytesConsumed = 0; assert bytesProduced == 0; // Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs. bytesProduced = SSL.bioFlushByteBuffer(networkBIO); if (bytesProduced > 0) { return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); } // There was a pending exception that we just delayed because there was something to produce left. // Throw it now and shutdown the engine. if (pendingException != null) { Throwable error = pendingException; pendingException = null; shutdown(); // Throw a new exception wrapping the pending exception, so the stacktrace is meaningful and // contains all the details. throw new SSLException(error); } for (; offset < endOffset; ++offset) { final ByteBuffer src = srcs[offset]; final int remaining = src.remaining(); if (remaining == 0) { continue; } final int bytesWritten; if (jdkCompatibilityMode) { // Write plaintext application data to the SSL engine. We don't have to worry about checking // if there is enough space if jdkCompatibilityMode because we only wrap at most // MAX_PLAINTEXT_LENGTH and we loop over the input before hand and check if there is space. bytesWritten = writePlaintextData(src, min(remaining, MAX_PLAINTEXT_LENGTH - bytesConsumed)); } else { // OpenSSL's SSL_write keeps state between calls. We should make sure the amount we attempt to // write is guaranteed to succeed so we don't have to worry about keeping state consistent // between calls. final int availableCapacityForWrap = dst.remaining() - bytesProduced - maxWrapOverhead; if (availableCapacityForWrap <= 0) { return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced); } bytesWritten = writePlaintextData(src, min(remaining, availableCapacityForWrap)); } // Determine how much encrypted data was generated. // // Even if SSL_write doesn't consume any application data it is possible that OpenSSL will // produce non-application data into the BIO. For example session tickets.... // See https://github.com/netty/netty/issues/10041 final int pendingNow = SSL.bioLengthByteBuffer(networkBIO); bytesProduced += bioLengthBefore - pendingNow; bioLengthBefore = pendingNow; if (bytesWritten > 0) { bytesConsumed += bytesWritten; if (jdkCompatibilityMode || bytesProduced == dst.remaining()) { return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); } } else { int sslError = SSL.getError(ssl, bytesWritten); if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { // This means the connection was shutdown correctly, close inbound and outbound if (!receivedShutdown) { closeAll(); bytesProduced += bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); // If we have filled up the dst buffer and we have not finished the handshake we should // try to wrap again. Otherwise we should only try to wrap again if there is still data // pending in SSL buffers. SSLEngineResult.HandshakeStatus hs = mayFinishHandshake( status != FINISHED ? bytesProduced == dst.remaining() ? NEED_WRAP : getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED); return newResult(hs, bytesConsumed, bytesProduced); } return newResult(NOT_HANDSHAKING, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_READ) { // If there is no pending data to read from BIO we should go back to event loop and try // to read more data [1]. It is also possible that event loop will detect the socket has // been closed. [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html return newResult(NEED_UNWRAP, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_WRITE) { // SSL_ERROR_WANT_WRITE typically means that the underlying transport is not writable // and we should set the ""want write"" flag on the selector and try again when the // underlying transport is writable [1]. However we are not directly writing to the // underlying transport and instead writing to a BIO buffer. The OpenSsl documentation // says we should do the following [1]: // // ""When using a buffering BIO, like a BIO pair, data must be written into or retrieved // out of the BIO before being able to continue."" // // In practice this means the destination buffer doesn't have enough space for OpenSSL // to write encrypted data to. This is an OVERFLOW condition. // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html if (bytesProduced > 0) { // If we produced something we should report this back and let the user call // wrap again. return newResult(NEED_WRAP, bytesConsumed, bytesProduced); } return newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { return newResult(NEED_TASK, bytesConsumed, bytesProduced); } else { // Everything else is considered as error throw shutdownWithError(""SSL_write"", sslError); } } } return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); } finally { SSL.bioClearByteBuffer(networkBIO); if (bioReadCopyBuf == null) { dst.position(dst.position() + bytesProduced); } else { assert bioReadCopyBuf.readableBytes() <= dst.remaining() : ""The destination buffer "" + dst + "" didn't have enough remaining space to hold the encrypted content in "" + bioReadCopyBuf; dst.put(bioReadCopyBuf.internalNioBuffer(bioReadCopyBuf.readerIndex(), bytesProduced)); bioReadCopyBuf.release(); } } } } private SSLEngineResult newResult(SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { return newResult(OK, hs, bytesConsumed, bytesProduced); } private SSLEngineResult newResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { // If isOutboundDone, then the data from the network BIO // was the close_notify message and all was consumed we are not required to wait // for the receipt the peer's close_notify message -- shutdown. if (isOutboundDone()) { if (isInboundDone()) { // If the inbound was done as well, we need to ensure we return NOT_HANDSHAKING to signal we are done. hs = NOT_HANDSHAKING; // As the inbound and the outbound is done we can shutdown the engine now. shutdown(); } return new SSLEngineResult(CLOSED, hs, bytesConsumed, bytesProduced); } if (hs == NEED_TASK) { // Set needTask to true so getHandshakeStatus() will return the correct value. needTask = true; } return new SSLEngineResult(status, hs, bytesConsumed, bytesProduced); } private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { return newResult(mayFinishHandshake(hs, bytesConsumed, bytesProduced), bytesConsumed, bytesProduced); } private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { return newResult(status, mayFinishHandshake(hs, bytesConsumed, bytesProduced), bytesConsumed, bytesProduced); } /** * Log the error, shutdown the engine and throw an exception. */ private SSLException shutdownWithError(String operations, int sslError) { return shutdownWithError(operations, sslError, SSL.getLastErrorNumber()); } private SSLException shutdownWithError(String operation, int sslError, int error) { if (logger.isDebugEnabled()) { String errorString = SSL.getErrorString(error); logger.debug(""{} failed with {}: OpenSSL error: {} {}"", operation, sslError, error, errorString); } // There was an internal error -- shutdown shutdown(); SSLException exception = newSSLExceptionForError(error); // If we have a pendingException stored already we should include it as well to help the user debug things. if (pendingException != null) { exception.initCause(pendingException); pendingException = null; } return exception; } private SSLEngineResult handleUnwrapException(int bytesConsumed, int bytesProduced, SSLException e) throws SSLException { int lastError = SSL.getLastErrorNumber(); if (lastError != 0) { return sslReadErrorResult(SSL.SSL_ERROR_SSL, lastError, bytesConsumed, bytesProduced); } throw e; } public final SSLEngineResult unwrap( final ByteBuffer[] srcs, int srcsOffset, final int srcsLength, final ByteBuffer[] dsts, int dstsOffset, final int dstsLength) throws SSLException { // Throw required runtime exceptions checkNotNullWithIAE(srcs, ""srcs""); if (srcsOffset >= srcs.length || srcsOffset + srcsLength > srcs.length) { throw new IndexOutOfBoundsException( ""offset: "" + srcsOffset + "", length: "" + srcsLength + "" (expected: offset <= offset + length <= srcs.length ("" + srcs.length + ""))""); } checkNotNullWithIAE(dsts, ""dsts""); if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) { throw new IndexOutOfBoundsException( ""offset: "" + dstsOffset + "", length: "" + dstsLength + "" (expected: offset <= offset + length <= dsts.length ("" + dsts.length + ""))""); } long capacity = 0; final int dstsEndOffset = dstsOffset + dstsLength; for (int i = dstsOffset; i < dstsEndOffset; i ++) { ByteBuffer dst = checkNotNullArrayParam(dsts[i], i, ""dsts""); if (dst.isReadOnly()) { throw new ReadOnlyBufferException(); } capacity += dst.remaining(); } final int srcsEndOffset = srcsOffset + srcsLength; long len = 0; for (int i = srcsOffset; i < srcsEndOffset; i++) { ByteBuffer src = checkNotNullArrayParam(srcs[i], i, ""srcs""); len += src.remaining(); } synchronized (this) { if (isInboundDone()) { return isOutboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_WRAP_CLOSED; } SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; HandshakeState oldHandshakeState = handshakeState; // Prepare OpenSSL to work in server mode and receive handshake if (handshakeState != HandshakeState.FINISHED) { if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { // Update accepted so we know we triggered the handshake via wrap handshakeState = HandshakeState.STARTED_IMPLICITLY; } status = handshake(); if (status == NEED_TASK) { return newResult(status, 0, 0); } if (status == NEED_WRAP) { return NEED_WRAP_OK; } // Check if the inbound is considered to be closed if so let us try to wrap again. if (isInboundDone) { return NEED_WRAP_CLOSED; } } int sslPending = sslPending0(); int packetLength; // The JDK implies that only a single SSL packet should be processed per unwrap call [1]. If we are in // JDK compatibility mode then we should honor this, but if not we just wrap as much as possible. If there // are multiple records or partial records this may reduce thrashing events through the pipeline. // [1] https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html if (jdkCompatibilityMode || // If the handshake was not finished before we entered the method, we also ensure we only // unwrap one record. We do this to ensure we not produce any extra data before the caller // of the method is able to observe handshake completion and react on it. oldHandshakeState != HandshakeState.FINISHED) { if (len < SSL_RECORD_HEADER_LENGTH) { return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); } packetLength = SslUtils.getEncryptedPacketLength(srcs, srcsOffset); if (packetLength == SslUtils.NOT_ENCRYPTED) { throw new NotSslRecordException(""not an SSL/TLS record""); } final int packetLengthDataOnly = packetLength - SSL_RECORD_HEADER_LENGTH; if (packetLengthDataOnly > capacity) { // Not enough space in the destination buffer so signal the caller that the buffer needs to be // increased. if (packetLengthDataOnly > MAX_RECORD_SIZE) { // The packet length MUST NOT exceed 2^14 [1]. However we do accommodate more data to support // legacy use cases which may violate this condition (e.g. OpenJDK's SslEngineImpl). If the max // length is exceeded we fail fast here to avoid an infinite loop due to the fact that we // won't allocate a buffer large enough. // [1] https://tools.ietf.org/html/rfc5246#section-6.2.1 throw new SSLException(""Illegal packet length: "" + packetLengthDataOnly + "" > "" + session.getApplicationBufferSize()); } else { session.tryExpandApplicationBufferSize(packetLengthDataOnly); } return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); } if (len < packetLength) { // We either don't have enough data to read the packet length or not enough for reading the whole // packet. return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); } } else if (len == 0 && sslPending <= 0) { return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); } else if (capacity == 0) { return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); } else { packetLength = (int) min(MAX_VALUE, len); } // This must always be the case when we reached here as if not we returned BUFFER_UNDERFLOW. assert srcsOffset < srcsEndOffset; // This must always be the case if we reached here. assert capacity > 0; // Number of produced bytes int bytesProduced = 0; int bytesConsumed = 0; try { srcLoop: for (;;) { ByteBuffer src = srcs[srcsOffset]; int remaining = src.remaining(); final ByteBuf bioWriteCopyBuf; int pendingEncryptedBytes; if (remaining == 0) { if (sslPending <= 0) { // We must skip empty buffers as BIO_write will return 0 if asked to write something // with length 0. if (++srcsOffset >= srcsEndOffset) { break; } continue; } else { bioWriteCopyBuf = null; pendingEncryptedBytes = SSL.bioLengthByteBuffer(networkBIO); } } else { // Write more encrypted data into the BIO. Ensure we only read one packet at a time as // stated in the SSLEngine javadocs. pendingEncryptedBytes = min(packetLength, remaining); try { bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes); } catch (SSLException e) { // Ensure we correctly handle the error stack. return handleUnwrapException(bytesConsumed, bytesProduced, e); } } try { for (;;) { ByteBuffer dst = dsts[dstsOffset]; if (!dst.hasRemaining()) { // No space left in the destination buffer, skip it. if (++dstsOffset >= dstsEndOffset) { break srcLoop; } continue; } int bytesRead; try { bytesRead = readPlaintextData(dst); } catch (SSLException e) { // Ensure we correctly handle the error stack. return handleUnwrapException(bytesConsumed, bytesProduced, e); } // We are directly using the ByteBuffer memory for the write, and so we only know what has // been consumed after we let SSL decrypt the data. At this point we should update the // number of bytes consumed, update the ByteBuffer position, and release temp ByteBuf. int localBytesConsumed = pendingEncryptedBytes - SSL.bioLengthByteBuffer(networkBIO); bytesConsumed += localBytesConsumed; packetLength -= localBytesConsumed; pendingEncryptedBytes -= localBytesConsumed; src.position(src.position() + localBytesConsumed); if (bytesRead > 0) { bytesProduced += bytesRead; if (!dst.hasRemaining()) { sslPending = sslPending0(); // Move to the next dst buffer as this one is full. if (++dstsOffset >= dstsEndOffset) { return sslPending > 0 ? newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced) : newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); } } else if (packetLength == 0 || jdkCompatibilityMode) { // We either consumed all data or we are in jdkCompatibilityMode and have consumed // a single TLS packet and should stop consuming until this method is called again. break srcLoop; } } else { int sslError = SSL.getError(ssl, bytesRead); if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { // break to the outer loop as we want to read more data which means we need to // write more to the BIO. break; } else if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { // This means the connection was shutdown correctly, close inbound and outbound if (!receivedShutdown) { closeAll(); } return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { return newResult(isInboundDone() ? CLOSED : OK, NEED_TASK, bytesConsumed, bytesProduced); } else { return sslReadErrorResult(sslError, SSL.getLastErrorNumber(), bytesConsumed, bytesProduced); } } } if (++srcsOffset >= srcsEndOffset) { break; } } finally { if (bioWriteCopyBuf != null) { bioWriteCopyBuf.release(); } } } } finally { SSL.bioClearByteBuffer(networkBIO); rejectRemoteInitiatedRenegotiation(); } // Check to see if we received a close_notify message from the peer. if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) { closeAll(); } return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); } } private boolean needWrapAgain(int stackError) { // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the // BIO first or can just shutdown and throw it now. // This is needed so we ensure close_notify etc is correctly send to the remote peer. // See https://github.com/netty/netty/issues/3900 if (SSL.bioLengthNonApplication(networkBIO) > 0) { // we seem to have data left that needs to be transferred and so the user needs // call wrap(...). Store the error so we can pick it up later. if (pendingException == null) { pendingException = newSSLExceptionForError(stackError); } else if (shouldAddSuppressed(pendingException, stackError)) { ThrowableUtil.addSuppressed(pendingException, newSSLExceptionForError(stackError)); } // We need to clear all errors so we not pick up anything that was left on the stack on the next // operation. Note that shutdownWithError(...) will cleanup the stack as well so its only needed here. SSL.clearError(); return true; } return false; } private SSLException newSSLExceptionForError(int stackError) { String message = SSL.getErrorString(stackError); return handshakeState == HandshakeState.FINISHED ? new OpenSslException(message, stackError) : new OpenSslHandshakeException(message, stackError); } private static boolean shouldAddSuppressed(Throwable target, int errorCode) { for (Throwable suppressed: ThrowableUtil.getSuppressed(target)) { if (suppressed instanceof NativeSslException && ((NativeSslException) suppressed).errorCode() == errorCode) { /// An exception with this errorCode was already added before. return false; } } return true; } private SSLEngineResult sslReadErrorResult(int error, int stackError, int bytesConsumed, int bytesProduced) throws SSLException { if (needWrapAgain(stackError)) { // There is something that needs to be send to the remote peer before we can teardown. // This is most likely some alert. return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced); } throw shutdownWithError(""SSL_read"", error, stackError); } private void closeAll() throws SSLException { receivedShutdown = true; closeOutbound(); closeInbound(); } private void rejectRemoteInitiatedRenegotiation() throws SSLHandshakeException { // As rejectRemoteInitiatedRenegotiation() is called in a finally block we also need to check if we shutdown // the engine before as otherwise SSL.getHandshakeCount(ssl) will throw an NPE if the passed in ssl is 0. // See https://github.com/netty/netty/issues/7353 if (!isDestroyed() && (!clientMode && SSL.getHandshakeCount(ssl) > 1 || // Let's allow to renegotiate once for client auth. clientMode && SSL.getHandshakeCount(ssl) > 2) && // As we may count multiple handshakes when TLSv1.3 is used we should just ignore this here as // renegotiation is not supported in TLSv1.3 as per spec. !SslProtocols.TLS_v1_3.equals(session.getProtocol()) && handshakeState == HandshakeState.FINISHED) { // TODO: In future versions me may also want to send a fatal_alert to the client and so notify it // that the renegotiation failed. shutdown(); throw new SSLHandshakeException(""remote-initiated renegotiation not allowed""); } } public final SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException { return unwrap(srcs, 0, srcs.length, dsts, 0, dsts.length); } private ByteBuffer[] singleSrcBuffer(ByteBuffer src) { singleSrcBuffer[0] = src; return singleSrcBuffer; } private void resetSingleSrcBuffer() { singleSrcBuffer[0] = null; } private ByteBuffer[] singleDstBuffer(ByteBuffer src) { singleDstBuffer[0] = src; return singleDstBuffer; } private void resetSingleDstBuffer() { singleDstBuffer[0] = null; } @Override public final synchronized SSLEngineResult unwrap( final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { try { return unwrap(singleSrcBuffer(src), 0, 1, dsts, offset, length); } finally { resetSingleSrcBuffer(); } } @Override public final synchronized SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException { try { return wrap(singleSrcBuffer(src), dst); } finally { resetSingleSrcBuffer(); } } @Override public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException { try { return unwrap(singleSrcBuffer(src), singleDstBuffer(dst)); } finally { resetSingleSrcBuffer(); resetSingleDstBuffer(); } } @Override public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException { try { return unwrap(singleSrcBuffer(src), dsts); } finally { resetSingleSrcBuffer(); } } private class TaskDecorator implements Runnable { protected final R task; TaskDecorator(R task) { this.task = task; } @Override public void run() { runAndResetNeedTask(task); } } private final class AsyncTaskDecorator extends TaskDecorator implements AsyncRunnable { AsyncTaskDecorator(AsyncTask task) { super(task); } @Override public void run(final Runnable runnable) { if (isDestroyed()) { // The engine was destroyed in the meantime, just return. return; } task.runAsync(new TaskDecorator(runnable)); } } private synchronized void runAndResetNeedTask(Runnable task) { try { if (isDestroyed()) { // The engine was destroyed in the meantime, just return. return; } task.run(); } finally { // The task was run, reset needTask to false so getHandshakeStatus() returns the correct value. needTask = false; } } @Override public final synchronized Runnable getDelegatedTask() { if (isDestroyed()) { return null; } final Runnable task = SSL.getTask(ssl); if (task == null) { return null; } if (task instanceof AsyncTask) { return new AsyncTaskDecorator((AsyncTask) task); } return new TaskDecorator(task); } @Override public final synchronized void closeInbound() throws SSLException { if (isInboundDone) { return; } isInboundDone = true; if (isOutboundDone()) { // Only call shutdown if there is no outbound data pending. // See https://github.com/netty/netty/issues/6167 shutdown(); } if (handshakeState != HandshakeState.NOT_STARTED && !receivedShutdown) { throw new SSLException( ""Inbound closed before receiving peer's close_notify: possible truncation attack?""); } } @Override public final synchronized boolean isInboundDone() { return isInboundDone; } @Override public final synchronized void closeOutbound() { if (outboundClosed) { return; } outboundClosed = true; if (handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()) { int mode = SSL.getShutdown(ssl); if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) { doSSLShutdown(); } } else { // engine closing before initial handshake shutdown(); } } /** * Attempt to call {@link SSL#shutdownSSL(long)}. * @return {@code false} if the call to {@link SSL#shutdownSSL(long)} was not attempted or returned an error. */ private boolean doSSLShutdown() { if (SSL.isInInit(ssl) != 0) { // Only try to call SSL_shutdown if we are not in the init state anymore. // Otherwise we will see 'error:140E0197:SSL routines:SSL_shutdown:shutdown while in init' in our logs. // // See also https://hg.nginx.org/nginx/rev/062c189fee20 return false; } int err = SSL.shutdownSSL(ssl); if (err < 0) { int sslErr = SSL.getError(ssl, err); if (sslErr == SSL.SSL_ERROR_SYSCALL || sslErr == SSL.SSL_ERROR_SSL) { if (logger.isDebugEnabled()) { int error = SSL.getLastErrorNumber(); logger.debug(""SSL_shutdown failed: OpenSSL error: {} {}"", error, SSL.getErrorString(error)); } // There was an internal error -- shutdown shutdown(); return false; } SSL.clearError(); } return true; } @Override public final synchronized boolean isOutboundDone() { // Check if there is anything left in the outbound buffer. // We need to ensure we only call SSL.pendingWrittenBytesInBIO(...) if the engine was not destroyed yet. return outboundClosed && (networkBIO == 0 || SSL.bioLengthNonApplication(networkBIO) == 0); } @Override public final String[] getSupportedCipherSuites() { return OpenSsl.AVAILABLE_CIPHER_SUITES.toArray(EMPTY_STRINGS); } @Override public final String[] getEnabledCipherSuites() { final String[] [MASK] ; final String[] enabled; final boolean tls13Enabled; synchronized (this) { if (!isDestroyed()) { enabled = SSL.getCiphers(ssl); int opts = SSL.getOptions(ssl); if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, SslProtocols.TLS_v1_3)) { [MASK] = OpenSsl.EXTRA_SUPPORTED_TLS_1_3_CIPHERS; tls13Enabled = true; } else { [MASK] = EMPTY_STRINGS; tls13Enabled = false; } } else { return EMPTY_STRINGS; } } if (enabled == null) { return EMPTY_STRINGS; } else { Set enabledSet = new LinkedHashSet(enabled.length + [MASK] .length); synchronized (this) { for (int i = 0; i < enabled.length; i++) { String mapped = toJavaCipherSuite(enabled[i]); final String cipher = mapped == null ? enabled[i] : mapped; if ((!tls13Enabled || !OpenSsl.isTlsv13Supported()) && SslUtils.isTLSv13Cipher(cipher)) { continue; } enabledSet.add(cipher); } Collections.addAll(enabledSet, [MASK] ); } return enabledSet.toArray(EMPTY_STRINGS); } } @Override public final void setEnabledCipherSuites(String[] cipherSuites) { checkNotNull(cipherSuites, ""cipherSuites""); final StringBuilder buf = new StringBuilder(); final StringBuilder bufTLSv13 = new StringBuilder(); CipherSuiteConverter.convertToCipherStrings(Arrays.asList(cipherSuites), buf, bufTLSv13, OpenSsl.isBoringSSL()); final String cipherSuiteSpec = buf.toString(); final String cipherSuiteSpecTLSv13 = bufTLSv13.toString(); if (!OpenSsl.isTlsv13Supported() && !cipherSuiteSpecTLSv13.isEmpty()) { throw new IllegalArgumentException(""TLSv1.3 is not supported by this java version.""); } synchronized (this) { if (!isDestroyed()) { try { // Set non TLSv1.3 ciphers. SSL.setCipherSuites(ssl, cipherSuiteSpec, false); if (OpenSsl.isTlsv13Supported()) { // Set TLSv1.3 ciphers. SSL.setCipherSuites(ssl, OpenSsl.checkTls13Ciphers(logger, cipherSuiteSpecTLSv13), true); } // We also need to update the enabled protocols to ensure we disable the protocol if there are // no compatible ciphers left. Set protocols = new HashSet(explicitlyEnabledProtocols.length); Collections.addAll(protocols, explicitlyEnabledProtocols); // We have no ciphers that are compatible with none-TLSv1.3, let us explicit disable all other // protocols. if (cipherSuiteSpec.isEmpty()) { protocols.remove(SslProtocols.TLS_v1); protocols.remove(SslProtocols.TLS_v1_1); protocols.remove(SslProtocols.TLS_v1_2); protocols.remove(SslProtocols.SSL_v3); protocols.remove(SslProtocols.SSL_v2); protocols.remove(SslProtocols.SSL_v2_HELLO); } // We have no ciphers that are compatible with TLSv1.3, let us explicit disable it. if (cipherSuiteSpecTLSv13.isEmpty()) { protocols.remove(SslProtocols.TLS_v1_3); } // Update the protocols but not cache the value. We only cache when we call it from the user // code or when we construct the engine. setEnabledProtocols0(protocols.toArray(EMPTY_STRINGS), false); } catch (Exception e) { throw new IllegalStateException(""failed to enable cipher suites: "" + cipherSuiteSpec, e); } } else { throw new IllegalStateException(""failed to enable cipher suites: "" + cipherSuiteSpec); } } } @Override public final String[] getSupportedProtocols() { return OpenSsl.SUPPORTED_PROTOCOLS_SET.toArray(EMPTY_STRINGS); } @Override public final String[] getEnabledProtocols() { List enabled = new ArrayList(6); // Seems like there is no way to explicit disable SSLv2Hello in openssl so it is always enabled enabled.add(SslProtocols.SSL_v2_HELLO); int opts; synchronized (this) { if (!isDestroyed()) { opts = SSL.getOptions(ssl); } else { return enabled.toArray(EMPTY_STRINGS); } } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1, SslProtocols.TLS_v1)) { enabled.add(SslProtocols.TLS_v1); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_1, SslProtocols.TLS_v1_1)) { enabled.add(SslProtocols.TLS_v1_1); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_2, SslProtocols.TLS_v1_2)) { enabled.add(SslProtocols.TLS_v1_2); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, SslProtocols.TLS_v1_3)) { enabled.add(SslProtocols.TLS_v1_3); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_SSLv2, SslProtocols.SSL_v2)) { enabled.add(SslProtocols.SSL_v2); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_SSLv3, SslProtocols.SSL_v3)) { enabled.add(SslProtocols.SSL_v3); } return enabled.toArray(EMPTY_STRINGS); } private static boolean isProtocolEnabled(int opts, int disableMask, String protocolString) { // We also need to check if the actual protocolString is supported as depending on the openssl API // implementations it may use a disableMask of 0 (BoringSSL is doing this for example). return (opts & disableMask) == 0 && OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocolString); } /** * {@inheritDoc} * TLS doesn't support a way to advertise non-contiguous versions from the client's perspective, and the client * just advertises the max supported version. The TLS protocol also doesn't support all different combinations of * discrete protocols, and instead assumes contiguous ranges. OpenSSL has some unexpected behavior * (e.g. handshake failures) if non-contiguous protocols are used even where there is a compatible set of protocols * and ciphers. For these reasons this method will determine the minimum protocol and the maximum protocol and * enabled a contiguous range from [min protocol, max protocol] in OpenSSL. */ @Override public final void setEnabledProtocols(String[] protocols) { setEnabledProtocols0(protocols, true); } private void setEnabledProtocols0(String[] protocols, boolean cache) { // This is correct from the API docs checkNotNullWithIAE(protocols, ""protocols""); int minProtocolIndex = OPENSSL_OP_NO_PROTOCOLS.length; int maxProtocolIndex = 0; for (String p: protocols) { if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) { throw new IllegalArgumentException(""Protocol "" + p + "" is not supported.""); } if (p.equals(SslProtocols.SSL_v2)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2; } } else if (p.equals(SslProtocols.SSL_v3)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3; } } else if (p.equals(SslProtocols.TLS_v1)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1; } } else if (p.equals(SslProtocols.TLS_v1_1)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1; } } else if (p.equals(SslProtocols.TLS_v1_2)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2; } } else if (p.equals(SslProtocols.TLS_v1_3)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3; } } } synchronized (this) { if (cache) { this.explicitlyEnabledProtocols = protocols; } if (!isDestroyed()) { // Clear out options which disable protocols SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 | SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2 | SSL.SSL_OP_NO_TLSv1_3); int opts = 0; for (int i = 0; i < minProtocolIndex; ++i) { opts |= OPENSSL_OP_NO_PROTOCOLS[i]; } assert maxProtocolIndex != MAX_VALUE; for (int i = maxProtocolIndex + 1; i < OPENSSL_OP_NO_PROTOCOLS.length; ++i) { opts |= OPENSSL_OP_NO_PROTOCOLS[i]; } // Disable protocols we do not want SSL.setOptions(ssl, opts); } else { throw new IllegalStateException(""failed to enable protocols: "" + Arrays.asList(protocols)); } } } @Override public final SSLSession getSession() { return session; } @Override public final synchronized void beginHandshake() throws SSLException { switch (handshakeState) { case STARTED_IMPLICITLY: checkEngineClosed(); // A user did not start handshake by calling this method by him/herself, // but handshake has been started already by wrap() or unwrap() implicitly. // Because it's the user's first time to call this method, it is unfair to // raise an exception. From the user's standpoint, he or she never asked // for renegotiation. handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user, calculateMaxWrapOverhead(); // we should raise an exception. break; case STARTED_EXPLICITLY: // Nothing to do as the handshake is not done yet. break; case FINISHED: throw new SSLException(""renegotiation unsupported""); case NOT_STARTED: handshakeState = HandshakeState.STARTED_EXPLICITLY; if (handshake() == NEED_TASK) { // Set needTask to true so getHandshakeStatus() will return the correct value. needTask = true; } calculateMaxWrapOverhead(); break; default: throw new Error(); } } private void checkEngineClosed() throws SSLException { if (isDestroyed()) { throw new SSLException(""engine closed""); } } private static SSLEngineResult.HandshakeStatus pendingStatus(int pendingStatus) { // Depending on if there is something left in the BIO we need to WRAP or UNWRAP return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP; } private static boolean isEmpty(Object[] arr) { return arr == null || arr.length == 0; } private static boolean isEmpty(byte[] cert) { return cert == null || cert.length == 0; } private SSLEngineResult.HandshakeStatus handshakeException() throws SSLException { if (SSL.bioLengthNonApplication(networkBIO) > 0) { // There is something pending, we need to consume it first via a WRAP so we don't loose anything. return NEED_WRAP; } Throwable exception = pendingException; assert exception != null; pendingException = null; shutdown(); if (exception instanceof SSLHandshakeException) { throw (SSLHandshakeException) exception; } SSLHandshakeException e = new SSLHandshakeException(""General OpenSslEngine problem""); e.initCause(exception); throw e; } /** * Should be called if the handshake will be failed due a callback that throws an exception. * This cause will then be used to give more details as part of the {@link SSLHandshakeException}. */ final void initHandshakeException(Throwable cause) { if (pendingException == null) { pendingException = cause; } else { ThrowableUtil.addSuppressed(pendingException, cause); } } private SSLEngineResult.HandshakeStatus handshake() throws SSLException { if (needTask) { return NEED_TASK; } if (handshakeState == HandshakeState.FINISHED) { return FINISHED; } checkEngineClosed(); if (pendingException != null) { // Let's call SSL.doHandshake(...) again in case there is some async operation pending that would fill the // outbound buffer. if (SSL.doHandshake(ssl) <= 0) { // Clear any error that was put on the stack by the handshake SSL.clearError(); } return handshakeException(); } // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier. engineMap.add(this); if (!sessionSet) { parentContext.sessionContext().setSessionFromCache(getPeerHost(), getPeerPort(), ssl); sessionSet = true; } if (lastAccessed == -1) { lastAccessed = System.currentTimeMillis(); } int code = SSL.doHandshake(ssl); if (code <= 0) { int sslError = SSL.getError(ssl, code); if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { return pendingStatus(SSL.bioLengthNonApplication(networkBIO)); } if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { return NEED_TASK; } if (needWrapAgain(SSL.getLastErrorNumber())) { // There is something that needs to be send to the remote peer before we can teardown. // This is most likely some alert. return NEED_WRAP; } // Check if we have a pending exception that was created during the handshake and if so throw it after // shutdown the connection. if (pendingException != null) { return handshakeException(); } // Everything else is considered as error throw shutdownWithError(""SSL_do_handshake"", sslError); } // We have produced more data as part of the handshake if this is the case the user should call wrap(...) if (SSL.bioLengthNonApplication(networkBIO) > 0) { return NEED_WRAP; } // if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished. session.handshakeFinished(SSL.getSessionId(ssl), SSL.getCipherForSSL(ssl), SSL.getVersion(ssl), SSL.getPeerCertificate(ssl), SSL.getPeerCertChain(ssl), SSL.getTime(ssl) * 1000L, parentContext.sessionTimeout() * 1000L); selectApplicationProtocol(); return FINISHED; } private SSLEngineResult.HandshakeStatus mayFinishHandshake( SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { return hs == NEED_UNWRAP && bytesProduced > 0 || hs == NEED_WRAP && bytesConsumed > 0 ? handshake() : mayFinishHandshake(hs != FINISHED ? getHandshakeStatus() : FINISHED); } private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status) throws SSLException { if (status == NOT_HANDSHAKING) { if (handshakeState != HandshakeState.FINISHED) { // If the status was NOT_HANDSHAKING and we not finished the handshake we need to call // SSL_do_handshake() again return handshake(); } if (!isDestroyed() && SSL.bioLengthNonApplication(networkBIO) > 0) { // We have something left that needs to be wrapped. return NEED_WRAP; } } return status; } @Override public final synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { // Check if we are in the initial handshake phase or shutdown phase if (needPendingStatus()) { if (needTask) { // There is a task outstanding return NEED_TASK; } return pendingStatus(SSL.bioLengthNonApplication(networkBIO)); } return NOT_HANDSHAKING; } private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) { // Check if we are in the initial handshake phase or shutdown phase if (needPendingStatus()) { if (needTask) { // There is a task outstanding return NEED_TASK; } return pendingStatus(pending); } return NOT_HANDSHAKING; } private boolean needPendingStatus() { return handshakeState != HandshakeState.NOT_STARTED && !isDestroyed() && (handshakeState != HandshakeState.FINISHED || isInboundDone() || isOutboundDone()); } /** * Converts the specified OpenSSL cipher suite to the Java cipher suite. */ private String toJavaCipherSuite(String openSslCipherSuite) { if (openSslCipherSuite == null) { return null; } String version = SSL.getVersion(ssl); String prefix = toJavaCipherSuitePrefix(version); return CipherSuiteConverter.toJava(openSslCipherSuite, prefix); } /** * Converts the protocol version string returned by {@link SSL#getVersion(long)} to protocol family string. */ private static String toJavaCipherSuitePrefix(String protocolVersion) { final char c; if (protocolVersion == null || protocolVersion.isEmpty()) { c = 0; } else { c = protocolVersion.charAt(0); } switch (c) { case 'T': return ""TLS""; case 'S': return ""SSL""; default: return ""UNKNOWN""; } } @Override public final void setUseClientMode(boolean clientMode) { if (clientMode != this.clientMode) { throw new UnsupportedOperationException(); } } @Override public final boolean getUseClientMode() { return clientMode; } @Override public final void setNeedClientAuth(boolean b) { setClientAuth(b ? ClientAuth.REQUIRE : ClientAuth.NONE); } @Override public final boolean getNeedClientAuth() { return clientAuth == ClientAuth.REQUIRE; } @Override public final void setWantClientAuth(boolean b) { setClientAuth(b ? ClientAuth.OPTIONAL : ClientAuth.NONE); } @Override public final boolean getWantClientAuth() { return clientAuth == ClientAuth.OPTIONAL; } /** * See SSL_set_verify and * {@link SSL#setVerify(long, int, int)}. */ @UnstableApi public final synchronized void setVerify(int verifyMode, int depth) { if (!isDestroyed()) { SSL.setVerify(ssl, verifyMode, depth); } } private void setClientAuth(ClientAuth mode) { if (clientMode) { return; } synchronized (this) { if (clientAuth == mode) { // No need to issue any JNI calls if the mode is the same return; } if (!isDestroyed()) { switch (mode) { case NONE: SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, ReferenceCountedOpenSslContext.VERIFY_DEPTH); break; case REQUIRE: SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, ReferenceCountedOpenSslContext.VERIFY_DEPTH); break; case OPTIONAL: SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, ReferenceCountedOpenSslContext.VERIFY_DEPTH); break; default: throw new Error(mode.toString()); } } clientAuth = mode; } } @Override public final void setEnableSessionCreation(boolean b) { if (b) { throw new UnsupportedOperationException(); } } @Override public final boolean getEnableSessionCreation() { return false; } @SuppressJava6Requirement(reason = ""Usage guarded by java version check"") @Override public final synchronized SSLParameters getSSLParameters() { SSLParameters sslParameters = super.getSSLParameters(); int version = PlatformDependent.javaVersion(); if (version >= 7) { sslParameters.setEndpointIdentificationAlgorithm(endPointIdentificationAlgorithm); Java7SslParametersUtils.setAlgorithmConstraints(sslParameters, algorithmConstraints); if (version >= 8) { if (sniHostNames != null) { Java8SslUtils.setSniHostNames(sslParameters, sniHostNames); } if (!isDestroyed()) { Java8SslUtils.setUseCipherSuitesOrder( sslParameters, (SSL.getOptions(ssl) & SSL.SSL_OP_CIPHER_SERVER_PREFERENCE) != 0); } Java8SslUtils.setSNIMatchers(sslParameters, matchers); } } return sslParameters; } @SuppressJava6Requirement(reason = ""Usage guarded by java version check"") @Override public final synchronized void setSSLParameters(SSLParameters sslParameters) { int version = PlatformDependent.javaVersion(); if (version >= 7) { if (sslParameters.getAlgorithmConstraints() != null) { throw new IllegalArgumentException(""AlgorithmConstraints are not supported.""); } boolean isDestroyed = isDestroyed(); if (version >= 8) { if (!isDestroyed) { if (clientMode) { final List sniHostNames = Java8SslUtils.getSniHostNames(sslParameters); for (String name: sniHostNames) { SSL.setTlsExtHostName(ssl, name); } this.sniHostNames = sniHostNames; } if (Java8SslUtils.getUseCipherSuitesOrder(sslParameters)) { SSL.setOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); } else { SSL.clearOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); } } matchers = sslParameters.getSNIMatchers(); } final String endPointIdentificationAlgorithm = sslParameters.getEndpointIdentificationAlgorithm(); if (!isDestroyed) { // If the user asks for hostname verification we must ensure we verify the peer. // If the user disables hostname verification we leave it up to the user to change the mode manually. if (clientMode && isEndPointVerificationEnabled(endPointIdentificationAlgorithm)) { SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, -1); } } this.endPointIdentificationAlgorithm = endPointIdentificationAlgorithm; algorithmConstraints = sslParameters.getAlgorithmConstraints(); } super.setSSLParameters(sslParameters); } private static boolean isEndPointVerificationEnabled(String endPointIdentificationAlgorithm) { return endPointIdentificationAlgorithm != null && !endPointIdentificationAlgorithm.isEmpty(); } private boolean isDestroyed() { return destroyed; } final boolean checkSniHostnameMatch(byte[] hostname) { return Java8SslUtils.checkSniHostnameMatch(matchers, hostname); } @Override public String getNegotiatedApplicationProtocol() { return applicationProtocol; } private static long bufferAddress(ByteBuffer b) { assert b.isDirect(); if (PlatformDependent.hasUnsafe()) { return PlatformDependent.directBufferAddress(b); } return Buffer.address(b); } /** * Select the application protocol used. */ private void selectApplicationProtocol() throws SSLException { ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior(); List protocols = apn.protocols(); String applicationProtocol; switch (apn.protocol()) { case NONE: break; // We always need to check for applicationProtocol == null as the remote peer may not support // the TLS extension or may have returned an empty selection. case ALPN: applicationProtocol = SSL.getAlpnSelected(ssl); if (applicationProtocol != null) { ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; case NPN: applicationProtocol = SSL.getNextProtoNegotiated(ssl); if (applicationProtocol != null) { ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; case NPN_AND_ALPN: applicationProtocol = SSL.getAlpnSelected(ssl); if (applicationProtocol == null) { applicationProtocol = SSL.getNextProtoNegotiated(ssl); } if (applicationProtocol != null) { ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; default: throw new Error(); } } private String selectApplicationProtocol(List protocols, ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior, String applicationProtocol) throws SSLException { if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT) { return applicationProtocol; } else { int size = protocols.size(); assert size > 0; if (protocols.contains(applicationProtocol)) { return applicationProtocol; } else { if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) { return protocols.get(size - 1); } else { throw new SSLException(""unknown protocol "" + applicationProtocol); } } } } final void setSessionId(OpenSslSessionId id) { session.setSessionId(id); } private static final X509Certificate[] JAVAX_CERTS_NOT_SUPPORTED = new X509Certificate[0]; private final class DefaultOpenSslSession implements OpenSslSession { private final OpenSslSessionContext sessionContext; // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any // thread. private X509Certificate[] x509PeerCerts; private Certificate[] peerCerts; private boolean valid = true; private String protocol; private String cipher; private OpenSslSessionId id = OpenSslSessionId.NULL_ID; private volatile long creationTime; private volatile int applicationBufferSize = MAX_PLAINTEXT_LENGTH; private volatile Certificate[] localCertificateChain; // lazy init for memory reasons private Map values; DefaultOpenSslSession(OpenSslSessionContext sessionContext) { this.sessionContext = sessionContext; } private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) { return new SSLSessionBindingEvent(session, name); } @Override public void setSessionId(OpenSslSessionId sessionId) { synchronized (ReferenceCountedOpenSslEngine.this) { if (this.id == OpenSslSessionId.NULL_ID) { this.id = sessionId; creationTime = System.currentTimeMillis(); } } } @Override public OpenSslSessionId sessionId() { synchronized (ReferenceCountedOpenSslEngine.this) { if (this.id == OpenSslSessionId.NULL_ID && !isDestroyed()) { byte[] sessionId = SSL.getSessionId(ssl); if (sessionId != null) { id = new OpenSslSessionId(sessionId); } } return id; } } @Override public void setLocalCertificate(Certificate[] localCertificate) { this.localCertificateChain = localCertificate; } @Override public byte[] getId() { return sessionId().cloneBytes(); } @Override public OpenSslSessionContext getSessionContext() { return sessionContext; } @Override public long getCreationTime() { synchronized (ReferenceCountedOpenSslEngine.this) { return creationTime; } } @Override public long getLastAccessedTime() { long lastAccessed = ReferenceCountedOpenSslEngine.this.lastAccessed; // if lastAccessed is -1 we will just return the creation time as the handshake was not started yet. return lastAccessed == -1 ? getCreationTime() : lastAccessed; } @Override public void invalidate() { synchronized (ReferenceCountedOpenSslEngine.this) { valid = false; sessionContext.removeFromCache(id); } } @Override public boolean isValid() { synchronized (ReferenceCountedOpenSslEngine.this) { return valid || sessionContext.isInCache(id); } } @Override public void putValue(String name, Object value) { checkNotNull(name, ""name""); checkNotNull(value, ""value""); final Object old; synchronized (this) { Map values = this.values; if (values == null) { // Use size of 2 to keep the memory overhead small values = this.values = new HashMap(2); } old = values.put(name, value); } if (value instanceof SSLSessionBindingListener) { // Use newSSLSessionBindingEvent so we always use the wrapper if needed. ((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name)); } notifyUnbound(old, name); } @Override public Object getValue(String name) { checkNotNull(name, ""name""); synchronized (this) { if (values == null) { return null; } return values.get(name); } } @Override public void removeValue(String name) { checkNotNull(name, ""name""); final Object old; synchronized (this) { Map values = this.values; if (values == null) { return; } old = values.remove(name); } notifyUnbound(old, name); } @Override public String[] getValueNames() { synchronized (this) { Map values = this.values; if (values == null || values.isEmpty()) { return EMPTY_STRINGS; } return values.keySet().toArray(EMPTY_STRINGS); } } private void notifyUnbound(Object value, String name) { if (value instanceof SSLSessionBindingListener) { // Use newSSLSessionBindingEvent so we always use the wrapper if needed. ((SSLSessionBindingListener) value).valueUnbound(newSSLSessionBindingEvent(name)); } } /** * Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by * the user. */ @Override public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate, byte[][] peerCertificateChain, long creationTime, long timeout) throws SSLException { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { this.creationTime = creationTime; if (this.id == OpenSslSessionId.NULL_ID) { this.id = id == null ? OpenSslSessionId.NULL_ID : new OpenSslSessionId(id); } this.cipher = toJavaCipherSuite(cipher); this.protocol = protocol; if (clientMode) { if (isEmpty(peerCertificateChain)) { peerCerts = EmptyArrays.EMPTY_CERTIFICATES; if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } } else { peerCerts = new Certificate[peerCertificateChain.length]; if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = new X509Certificate[peerCertificateChain.length]; } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } initCerts(peerCertificateChain, 0); } } else { // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer // certificate. We use SSL_get_peer_certificate to get it in this case and add it to our // array later. // // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html if (isEmpty(peerCertificate)) { peerCerts = EmptyArrays.EMPTY_CERTIFICATES; x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; } else { if (isEmpty(peerCertificateChain)) { peerCerts = new Certificate[] {new LazyX509Certificate(peerCertificate)}; if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = new X509Certificate[] { new LazyJavaxX509Certificate(peerCertificate) }; } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } } else { peerCerts = new Certificate[peerCertificateChain.length + 1]; peerCerts[0] = new LazyX509Certificate(peerCertificate); if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = new X509Certificate[peerCertificateChain.length + 1]; x509PeerCerts[0] = new LazyJavaxX509Certificate(peerCertificate); } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } initCerts(peerCertificateChain, 1); } } } calculateMaxWrapOverhead(); handshakeState = HandshakeState.FINISHED; } else { throw new SSLException(""Already closed""); } } } private void initCerts(byte[][] chain, int startPos) { for (int i = 0; i < chain.length; i++) { int certPos = startPos + i; peerCerts[certPos] = new LazyX509Certificate(chain[i]); if (x509PeerCerts != JAVAX_CERTS_NOT_SUPPORTED) { x509PeerCerts[certPos] = new LazyJavaxX509Certificate(chain[i]); } } } @Override public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { synchronized (ReferenceCountedOpenSslEngine.this) { if (isEmpty(peerCerts)) { throw new SSLPeerUnverifiedException(""peer not verified""); } return peerCerts.clone(); } } @Override public Certificate[] getLocalCertificates() { Certificate[] localCerts = this.localCertificateChain; if (localCerts == null) { return null; } return localCerts.clone(); } @Override public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { synchronized (ReferenceCountedOpenSslEngine.this) { if (x509PeerCerts == JAVAX_CERTS_NOT_SUPPORTED) { // Not supported by the underlying JDK, so just throw. This is fine in terms of the API // contract. See SSLSession.html#getPeerCertificateChain(). throw new UnsupportedOperationException(); } if (isEmpty(x509PeerCerts)) { throw new SSLPeerUnverifiedException(""peer not verified""); } return x509PeerCerts.clone(); } } @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { Certificate[] peer = getPeerCertificates(); // No need for null or length > 0 is needed as this is done in getPeerCertificates() // already. return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal(); } @Override public Principal getLocalPrincipal() { Certificate[] local = this.localCertificateChain; if (local == null || local.length == 0) { return null; } return ((java.security.cert.X509Certificate) local[0]).getSubjectX500Principal(); } @Override public String getCipherSuite() { synchronized (ReferenceCountedOpenSslEngine.this) { if (cipher == null) { return SslUtils.INVALID_CIPHER; } return cipher; } } @Override public String getProtocol() { String protocol = this.protocol; if (protocol == null) { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { protocol = SSL.getVersion(ssl); } else { protocol = StringUtil.EMPTY_STRING; } } } return protocol; } @Override public String getPeerHost() { return ReferenceCountedOpenSslEngine.this.getPeerHost(); } @Override public int getPeerPort() { return ReferenceCountedOpenSslEngine.this.getPeerPort(); } @Override public int getPacketBufferSize() { return SSL.SSL_MAX_ENCRYPTED_LENGTH; } @Override public int getApplicationBufferSize() { return applicationBufferSize; } @Override public void tryExpandApplicationBufferSize(int packetLengthDataOnly) { if (packetLengthDataOnly > MAX_PLAINTEXT_LENGTH && applicationBufferSize != MAX_RECORD_SIZE) { applicationBufferSize = MAX_RECORD_SIZE; } } @Override public String toString() { return ""DefaultOpenSslSession{"" + ""sessionContext="" + sessionContext + "", id="" + id + '}'; } } private interface NativeSslException { int errorCode(); } private static final class OpenSslException extends SSLException implements NativeSslException { private final int errorCode; OpenSslException(String reason, int errorCode) { super(reason); this.errorCode = errorCode; } @Override public int errorCode() { return errorCode; } } private static final class OpenSslHandshakeException extends SSLHandshakeException implements NativeSslException { private final int errorCode; OpenSslHandshakeException(String reason, int errorCode) { super(reason); this.errorCode = errorCode; } @Override public int errorCode() { return errorCode; } } } ","extraCiphers " "// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skyframe; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.testing.EqualsTester; import com.google.devtools.build.lib.actions.FileValue; import com.google.devtools.build.lib.actions.ThreadStateReceiver; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.ServerDirectories; import com.google.devtools.build.lib.analysis.util.AnalysisMock; import com.google.devtools.build.lib.clock.BlazeClock; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.events.NullEventHandler; import com.google.devtools.build.lib.io.FileSymlinkCycleUniquenessFunction; import com.google.devtools.build.lib.packages.RuleClassProvider; import com.google.devtools.build.lib.packages.WorkspaceFileValue; import com.google.devtools.build.lib.pkgcache.PathPackageLocator; import com.google.devtools.build.lib.rules.repository.LocalRepositoryFunction; import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule; import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction; import com.google.devtools.build.lib.rules.repository.RepositoryFunction; import com.google.devtools.build.lib.skyframe.ContainingPackageLookupValue.ContainingPackage; import com.google.devtools.build.lib.skyframe.ContainingPackageLookupValue.NoContainingPackage; import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction; import com.google.devtools.build.lib.skyframe.PackageFunction.GlobbingStrategy; import com.google.devtools.build.lib.skyframe.PackageLookupFunction.CrossRepositoryLabelViolationStrategy; import com.google.devtools.build.lib.skyframe.PackageLookupValue.ErrorReason; import com.google.devtools.build.lib.testutil.FoundationTestCase; import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; import com.google.devtools.build.lib.vfs.FileStateKey; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.build.lib.vfs.SyscallCache; import com.google.devtools.build.skyframe.EvaluationContext; import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator; import com.google.devtools.build.skyframe.MemoizingEvaluator; import com.google.devtools.build.skyframe.RecordingDifferencer; import com.google.devtools.build.skyframe.SequencedRecordingDifferencer; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyFunctionName; import com.google.devtools.build.skyframe.SkyKey; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import net.starlark.java.eval.StarlarkSemantics; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link ContainingPackageLookupFunction}. */ @RunWith(JUnit4.class) public class ContainingPackageLookupFunctionTest extends FoundationTestCase { private AtomicReference> deletedPackages; private MemoizingEvaluator evaluator; @Before public final void setUp() throws Exception { AnalysisMock analysisMock = AnalysisMock.get(); AtomicReference pkgLocator = new AtomicReference<>( new PathPackageLocator( outputBase, ImmutableList.of(Root.fromPath(rootDirectory)), BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY)); deletedPackages = new AtomicReference<>(ImmutableSet.of()); BlazeDirectories directories = new BlazeDirectories( new ServerDirectories(rootDirectory, outputBase, outputBase), rootDirectory, /* defaultSystemJavabase= */ null, analysisMock.getProductName()); ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting( pkgLocator, ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS, directories); Map skyFunctions = new HashMap<>(); skyFunctions.put(SkyFunctions.CONTAINING_PACKAGE_LOOKUP, new ContainingPackageLookupFunction()); skyFunctions.put( SkyFunctions.PACKAGE_LOOKUP, new PackageLookupFunction( deletedPackages, CrossRepositoryLabelViolationStrategy.ERROR, BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY, BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER)); skyFunctions.put( SkyFunctions.PACKAGE, new PackageFunction( null, null, null, null, null, /*packageProgress=*/ null, PackageFunction.ActionOnIOExceptionReadingBuildFile.UseOriginalIOException.INSTANCE, /* shouldUseRepoDotBazel= */ true, GlobbingStrategy.SKYFRAME_HYBRID, k -> ThreadStateReceiver.NULL_INSTANCE)); skyFunctions.put( SkyFunctions.IGNORED_PACKAGE_PREFIXES, new IgnoredPackagePrefixesFunction( /*ignoredPackagePrefixesFile=*/ PathFragment.EMPTY_FRAGMENT)); skyFunctions.put( FileStateKey.FILE_STATE, new FileStateFunction( Suppliers.ofInstance(new TimestampGranularityMonitor(BlazeClock.instance())), SyscallCache.NO_CACHE, externalFilesHelper)); skyFunctions.put(FileValue.FILE, new FileFunction(pkgLocator, directories)); skyFunctions.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction()); skyFunctions.put( SkyFunctions.DIRECTORY_LISTING_STATE, new DirectoryListingStateFunction(externalFilesHelper, SyscallCache.NO_CACHE)); RuleClassProvider ruleClassProvider = analysisMock.createRuleClassProvider(); skyFunctions.put( WorkspaceFileValue.WORKSPACE_FILE, new WorkspaceFileFunction( ruleClassProvider, analysisMock .getPackageFactoryBuilderForTesting(directories) .build(ruleClassProvider, fileSystem), directories, /*bzlLoadFunctionForInlining=*/ null)); skyFunctions.put( SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER)); skyFunctions.put( SkyFunctions.LOCAL_REPOSITORY_LOOKUP, new LocalRepositoryLookupFunction(BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER)); skyFunctions.put( FileSymlinkCycleUniquenessFunction.NAME, new FileSymlinkCycleUniquenessFunction()); ImmutableMap repositoryHandlers = ImmutableMap.of(LocalRepositoryRule.NAME, new LocalRepositoryFunction()); skyFunctions.put( SkyFunctions.REPOSITORY_DIRECTORY, new RepositoryDelegatorFunction( repositoryHandlers, null, new AtomicBoolean(true), ImmutableMap::of, directories, BazelSkyframeExecutorConstants.EXTERNAL_PACKAGE_HELPER)); RecordingDifferencer differencer = new SequencedRecordingDifferencer(); evaluator = new InMemoryMemoizingEvaluator(skyFunctions, differencer); PrecomputedValue.BUILD_ID.set(differencer, UUID.randomUUID()); PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get()); PrecomputedValue.STARLARK_SEMANTICS.set(differencer, StarlarkSemantics.DEFAULT); RepositoryDelegatorFunction.REPOSITORY_OVERRIDES.set(differencer, ImmutableMap.of()); RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING.set( differencer, RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY); RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE.set( differencer, Optional.empty()); } private ContainingPackageLookupValue lookupContainingPackage(String packageName) throws InterruptedException { return lookupContainingPackage(PackageIdentifier.createInMainRepo(packageName)); } private ContainingPackageLookupValue lookupContainingPackage(PackageIdentifier packageIdentifier) throws InterruptedException { SkyKey key = ContainingPackageLookupValue.key(packageIdentifier); EvaluationContext evaluationContext = EvaluationContext.newBuilder() .setKeepGoing(false) .setParallelism(SkyframeExecutor.DEFAULT_THREAD_COUNT) .setEventHandler(NullEventHandler.INSTANCE) .build(); return evaluator .evaluate(ImmutableList.of(key), evaluationContext) .get(key); } private PackageLookupValue lookupPackage(PackageIdentifier packageIdentifier) throws InterruptedException { SkyKey key = PackageLookupValue.key(packageIdentifier); EvaluationContext evaluationContext = EvaluationContext.newBuilder() .setKeepGoing(false) .setParallelism(SkyframeExecutor.DEFAULT_THREAD_COUNT) .setEventHandler(NullEventHandler.INSTANCE) .build(); return evaluator .evaluate(ImmutableList.of(key), evaluationContext) .get(key); } @Test public void testNoContainingPackage() throws Exception { ContainingPackageLookupValue value = lookupContainingPackage(""a/b""); assertThat(value.hasContainingPackage()).isFalse(); } @Test public void testContainingPackageIsParent() throws Exception { scratch.file(""a/BUILD""); ContainingPackageLookupValue value = lookupContainingPackage(""a/b""); assertThat(value.hasContainingPackage()).isTrue(); assertThat(value.getContainingPackageName()).isEqualTo(PackageIdentifier.createInMainRepo(""a"")); assertThat(value.getContainingPackageRoot()).isEqualTo(Root.fromPath(rootDirectory)); } @Test public void testContainingPackageIsSelf() throws Exception { scratch.file(""a/b/BUILD""); ContainingPackageLookupValue value = lookupContainingPackage(""a/b""); assertThat(value.hasContainingPackage()).isTrue(); assertThat(value.getContainingPackageName()) .isEqualTo(PackageIdentifier.createInMainRepo(""a/b"")); assertThat(value.getContainingPackageRoot()).isEqualTo(Root.fromPath(rootDirectory)); } @Test public void testContainingPackageIsExternalRepositoryViaExternalRepository() throws Exception { scratch.overwriteFile(""WORKSPACE"", ""local_repository(name='a', path='a')""); scratch.file(""a/WORKSPACE""); scratch.file(""a/BUILD""); scratch.file(""a/b/BUILD""); ContainingPackageLookupValue value = lookupContainingPackage( PackageIdentifier.create(RepositoryName.create(""a""), PathFragment.create(""b""))); assertThat(value.hasContainingPackage()).isTrue(); assertThat(value.getContainingPackageName()) .isEqualTo(PackageIdentifier.create(RepositoryName.create(""a""), PathFragment.create(""b""))); } @Test public void testContainingPackageIsExternalRepositoryViaLocalPath() throws Exception { scratch.overwriteFile(""WORKSPACE"", ""local_repository(name='a', path='a')""); scratch.file(""a/WORKSPACE""); scratch.file(""a/BUILD""); scratch.file(""a/b/BUILD""); ContainingPackageLookupValue value = lookupContainingPackage(""a/b""); assertThat(value.hasContainingPackage()).isTrue(); assertThat(value.getContainingPackageName()) .isEqualTo(PackageIdentifier.create(RepositoryName.create(""a""), PathFragment.create(""b""))); } @Test public void testEqualsAndHashCodeContract() { ContainingPackageLookupValue valueA1 = ContainingPackageLookupValue.NONE; ContainingPackageLookupValue valueA2 = ContainingPackageLookupValue.NONE; ContainingPackageLookupValue valueB1 = ContainingPackageLookupValue.withContainingPackage( PackageIdentifier.createInMainRepo(""b""), Root.fromPath(rootDirectory)); ContainingPackageLookupValue valueB2 = ContainingPackageLookupValue.withContainingPackage( PackageIdentifier.createInMainRepo(""b""), Root.fromPath(rootDirectory)); PackageIdentifier [MASK] = PackageIdentifier.createInMainRepo(""c""); ContainingPackageLookupValue valueC1 = ContainingPackageLookupValue.withContainingPackage( [MASK] , Root.fromPath(rootDirectory)); ContainingPackageLookupValue valueC2 = ContainingPackageLookupValue.withContainingPackage( [MASK] , Root.fromPath(rootDirectory)); ContainingPackageLookupValue valueCOther = ContainingPackageLookupValue.withContainingPackage( [MASK] , Root.fromPath(rootDirectory.getRelative(""other_root""))); new EqualsTester() .addEqualityGroup(valueA1, valueA2) .addEqualityGroup(valueB1, valueB2) .addEqualityGroup(valueC1, valueC2) .addEqualityGroup(valueCOther) .testEquals(); } @Test public void testNonExistentExternalRepositoryErrorReason() throws Exception { PackageIdentifier identifier = PackageIdentifier.create(""some_repo"", PathFragment.create("":atarget"")); ContainingPackageLookupValue value = lookupContainingPackage(identifier); assertThat(value.hasContainingPackage()).isFalse(); assertThat(value.getClass()).isEqualTo(NoContainingPackage.class); assertThat(value.getReasonForNoContainingPackage()) .isEqualTo( ""The repository '@some_repo' could not be resolved: Repository '@some_repo' is not"" + "" defined""); } @Test public void testInvalidPackageLabelErrorReason() throws Exception { ContainingPackageLookupValue value = lookupContainingPackage(""invalidpackagename:42/BUILD""); assertThat(value.hasContainingPackage()).isFalse(); assertThat(value.getClass()).isEqualTo(NoContainingPackage.class); // As for invalid package name we continue to climb up the parent packages, // we will find the top-level package with the path """" - empty string. assertThat(value.getReasonForNoContainingPackage()).isNull(); } @Test public void testDeletedPackageErrorReason() throws Exception { PackageIdentifier identifier = PackageIdentifier.createInMainRepo(""deletedpackage""); deletedPackages.set(ImmutableSet.of(identifier)); scratch.file(""BUILD""); PackageLookupValue packageLookupValue = lookupPackage(identifier); assertThat(packageLookupValue.packageExists()).isFalse(); assertThat(packageLookupValue.getErrorReason()).isEqualTo(ErrorReason.DELETED_PACKAGE); assertThat(packageLookupValue.getErrorMsg()) .isEqualTo(""Package is considered deleted due to --deleted_packages""); ContainingPackageLookupValue value = lookupContainingPackage(identifier); assertThat(value.hasContainingPackage()).isTrue(); assertThat(value.getContainingPackageName().toString()).isEmpty(); assertThat(value.getClass()).isEqualTo(ContainingPackage.class); } @Test public void testNoBuildFileErrorReason() throws Exception { ContainingPackageLookupValue value = lookupContainingPackage(""abc""); assertThat(value.hasContainingPackage()).isFalse(); assertThat(value.getClass()).isEqualTo(NoContainingPackage.class); assertThat(value.getReasonForNoContainingPackage()).isNull(); } } ","cFrag " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.rpc; import org.apache.dubbo.common.Experimental; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.threadlocal.InternalThreadLocal; import org.apache.dubbo.common.utils.StringUtils; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; /** * Thread local context. (API, ThreadLocal, ThreadSafe) *

* Note: RpcContext is a temporary state holder. States in RpcContext changes every time when request is sent or received. *

* There are four kinds of RpcContext, which are ServerContext, ClientAttachment, ServerAttachment and ServiceContext. *

* ServiceContext: Using to pass environment parameters in the whole invocation. For example, `remotingApplicationName`, * `remoteAddress`, etc. {@link RpcServiceContext} * ClientAttachment, ServerAttachment and ServiceContext are using to transfer attachments. * Imaging a situation like this, A is calling B, and B will call C, after that, B wants to return some attachments back to A. * ClientAttachment is using to pass attachments to next hop as a consumer. ( A --> B , in A side) * ServerAttachment is using to fetch attachments from previous hop as a provider. ( A --> B , in B side) * ServerContext is using to return some attachments back to client as a provider. ( A <-- B , in B side) * The reason why using `ServiceContext` is to make API compatible with previous. * * @export * @see org.apache.dubbo.rpc.filter.ContextFilter */ public class RpcContext { private static final RpcContext AGENT = new RpcContext(); /** * use internal thread local to improve performance */ private static final InternalThreadLocal CLIENT_RESPONSE_LOCAL = new InternalThreadLocal() { @Override protected RpcContextAttachment initialValue() { return new RpcContextAttachment(); } }; private static final InternalThreadLocal SERVER_RESPONSE_LOCAL = new InternalThreadLocal() { @Override protected RpcContextAttachment initialValue() { return new RpcContextAttachment(); } }; private static final InternalThreadLocal CLIENT_ATTACHMENT = new InternalThreadLocal() { @Override protected RpcContextAttachment initialValue() { return new RpcContextAttachment(); } }; private static final InternalThreadLocal SERVER_ATTACHMENT = new InternalThreadLocal() { @Override protected RpcContextAttachment initialValue() { return new RpcContextAttachment(); } }; private static final InternalThreadLocal SERVICE_CONTEXT = new InternalThreadLocal() { @Override protected RpcServiceContext initialValue() { return new RpcServiceContext(); } }; /** * use by cancel call */ private static final InternalThreadLocal CANCELLATION_CONTEXT = new InternalThreadLocal() { @Override protected CancellationContext initialValue() { return new CancellationContext(); } }; public static CancellationContext getCancellationContext() { return CANCELLATION_CONTEXT.get(); } public static void removeCancellationContext() { CANCELLATION_CONTEXT.remove(); } public static void restoreCancellationContext(CancellationContext oldContext) { CANCELLATION_CONTEXT.set(oldContext); } private boolean remove = true; protected RpcContext() { } /** * get server side context. ( A <-- B , in B side) * * @return server context */ public static RpcContextAttachment getServerContext() { return new RpcServerContextAttachment(); } /** * remove server side context. * * @see org.apache.dubbo.rpc.filter.ContextFilter */ public static RpcContextAttachment getClientResponseContext() { return CLIENT_RESPONSE_LOCAL.get(); } public static RpcContextAttachment getServerResponseContext() { return SERVER_RESPONSE_LOCAL.get(); } public static void removeClientResponseContext() { CLIENT_RESPONSE_LOCAL.remove(); } public static void removeServerResponseContext() { SERVER_RESPONSE_LOCAL.remove(); } /** * get context. * * @return context */ @Deprecated public static RpcContext getContext() { return AGENT; } /** * get consumer side attachment ( A --> B , in A side) * * @return context */ public static RpcContextAttachment getClientAttachment() { return CLIENT_ATTACHMENT.get(); } /** * get provider side attachment from consumer ( A --> B , in B side) * * @return context */ public static RpcContextAttachment getServerAttachment() { return SERVER_ATTACHMENT.get(); } public static void removeServerContext() { RpcContextAttachment rpcContextAttachment = RpcContext.getServerContext(); for(String key : rpcContextAttachment.attachments.keySet()) { rpcContextAttachment.remove(key); } } public boolean canRemove() { return remove; } public void clearAfterEachInvoke(boolean remove) { this.remove = remove; } /** * Using to pass environment parameters in the whole invocation. For example, `remotingApplicationName`, * `remoteAddress`, etc. {@link RpcServiceContext} * * @return context */ public static RpcServiceContext getServiceContext() { return SERVICE_CONTEXT.get(); } public static RpcServiceContext getCurrentServiceContext() { return SERVICE_CONTEXT.getWithoutInitialize(); } public static void removeServiceContext() { SERVICE_CONTEXT.remove(); } public static void removeClientAttachment() { if (CLIENT_ATTACHMENT.get().canRemove()) { CLIENT_ATTACHMENT.remove(); } } public static void removeServerAttachment() { if (SERVER_ATTACHMENT.get().canRemove()) { SERVER_ATTACHMENT.remove(); } } /** * customized for internal use. */ public static void removeContext() { if (CLIENT_ATTACHMENT.get().canRemove()) { CLIENT_ATTACHMENT.remove(); } if (SERVER_ATTACHMENT.get().canRemove()) { SERVER_ATTACHMENT.remove(); } CLIENT_RESPONSE_LOCAL.remove(); SERVER_RESPONSE_LOCAL.remove(); SERVICE_CONTEXT.remove(); CANCELLATION_CONTEXT.remove(); } /** * Get the request object of the underlying RPC protocol, e.g. HttpServletRequest * * @return null if the underlying protocol doesn't provide support for getting request */ public Object getRequest() { return SERVICE_CONTEXT.get().getRequest(); } public void setRequest(Object request) { SERVICE_CONTEXT.get().setRequest(request); } /** * Get the request object of the underlying RPC protocol, e.g. HttpServletRequest * * @return null if the underlying protocol doesn't provide support for getting request or the request is not of the specified type */ @SuppressWarnings(""unchecked"") public T getRequest(Class clazz) { return SERVICE_CONTEXT.get().getRequest(clazz); } /** * Get the [MASK] object of the underlying RPC protocol, e.g. HttpServletResponse * * @return null if the underlying protocol doesn't provide support for getting [MASK] */ public Object getResponse() { return SERVICE_CONTEXT.get().getResponse(); } public void setResponse(Object [MASK] ) { SERVICE_CONTEXT.get().setResponse( [MASK] ); } /** * Get the [MASK] object of the underlying RPC protocol, e.g. HttpServletResponse * * @return null if the underlying protocol doesn't provide support for getting [MASK] or the [MASK] is not of the specified type */ @SuppressWarnings(""unchecked"") public T getResponse(Class clazz) { return SERVICE_CONTEXT.get().getResponse(clazz); } /** * is provider side. * * @return provider side. */ public boolean isProviderSide() { return SERVICE_CONTEXT.get().isProviderSide(); } /** * is consumer side. * * @return consumer side. */ public boolean isConsumerSide() { return SERVICE_CONTEXT.get().isConsumerSide(); } /** * get CompletableFuture. * * @param * @return future */ @SuppressWarnings(""unchecked"") public CompletableFuture getCompletableFuture() { return SERVICE_CONTEXT.get().getCompletableFuture(); } /** * get future. * * @param * @return future */ @SuppressWarnings(""unchecked"") public Future getFuture() { return SERVICE_CONTEXT.get().getFuture(); } /** * set future. * * @param future */ public void setFuture(CompletableFuture future) { SERVICE_CONTEXT.get().setFuture(future); } public List getUrls() { return SERVICE_CONTEXT.get().getUrls(); } public void setUrls(List urls) { SERVICE_CONTEXT.get().setUrls(urls); } public URL getUrl() { return SERVICE_CONTEXT.get().getUrl(); } public void setUrl(URL url) { SERVICE_CONTEXT.get().setUrl(url); } /** * get method name. * * @return method name. */ public String getMethodName() { return SERVICE_CONTEXT.get().getMethodName(); } public void setMethodName(String methodName) { SERVICE_CONTEXT.get().setMethodName(methodName); } /** * get parameter types. * * @serial */ public Class[] getParameterTypes() { return SERVICE_CONTEXT.get().getParameterTypes(); } public void setParameterTypes(Class[] parameterTypes) { SERVICE_CONTEXT.get().setParameterTypes(parameterTypes); } /** * get arguments. * * @return arguments. */ public Object[] getArguments() { return SERVICE_CONTEXT.get().getArguments(); } public void setArguments(Object[] arguments) { SERVICE_CONTEXT.get().setArguments(arguments); } /** * set local address. * * @param host * @param port * @return context */ public RpcContext setLocalAddress(String host, int port) { return SERVICE_CONTEXT.get().setLocalAddress(host, port); } /** * get local address. * * @return local address */ public InetSocketAddress getLocalAddress() { return SERVICE_CONTEXT.get().getLocalAddress(); } /** * set local address. * * @param address * @return context */ public RpcContext setLocalAddress(InetSocketAddress address) { return SERVICE_CONTEXT.get().setLocalAddress(address); } public String getLocalAddressString() { return SERVICE_CONTEXT.get().getLocalAddressString(); } /** * get local host name. * * @return local host name */ public String getLocalHostName() { return SERVICE_CONTEXT.get().getLocalHostName(); } /** * set remote address. * * @param host * @param port * @return context */ public RpcContext setRemoteAddress(String host, int port) { return SERVICE_CONTEXT.get().setRemoteAddress(host, port); } /** * get remote address. * * @return remote address */ public InetSocketAddress getRemoteAddress() { return SERVICE_CONTEXT.get().getRemoteAddress(); } /** * set remote address. * * @param address * @return context */ public RpcContext setRemoteAddress(InetSocketAddress address) { return SERVICE_CONTEXT.get().setRemoteAddress(address); } public String getRemoteApplicationName() { return SERVICE_CONTEXT.get().getRemoteApplicationName(); } public RpcContext setRemoteApplicationName(String remoteApplicationName) { return SERVICE_CONTEXT.get().setRemoteApplicationName(remoteApplicationName); } /** * get remote address string. * * @return remote address string. */ public String getRemoteAddressString() { return SERVICE_CONTEXT.get().getRemoteAddressString(); } /** * get remote host name. * * @return remote host name */ public String getRemoteHostName() { return SERVICE_CONTEXT.get().getRemoteHostName(); } /** * get local host. * * @return local host */ public String getLocalHost() { return SERVICE_CONTEXT.get().getLocalHost(); } /** * get local port. * * @return port */ public int getLocalPort() { return SERVICE_CONTEXT.get().getLocalPort(); } /** * get remote host. * * @return remote host */ public String getRemoteHost() { return SERVICE_CONTEXT.get().getRemoteHost(); } /** * get remote port. * * @return remote port */ public int getRemotePort() { return SERVICE_CONTEXT.get().getRemotePort(); } /** * also see {@link #getObjectAttachment(String)}. * * @param key * @return attachment */ public String getAttachment(String key) { String client = CLIENT_ATTACHMENT.get().getAttachment(key); if (StringUtils.isEmpty(client)) { return SERVER_ATTACHMENT.get().getAttachment(key); } return client; } /** * get attachment. * * @param key * @return attachment */ @Experimental(""Experiment api for supporting Object transmission"") public Object getObjectAttachment(String key) { Object client = CLIENT_ATTACHMENT.get().getObjectAttachment(key); if (client == null) { return SERVER_ATTACHMENT.get().getObjectAttachment(key); } return client; } /** * set attachment. * * @param key * @param value * @return context */ public RpcContext setAttachment(String key, String value) { return setObjectAttachment(key, value); } public RpcContext setAttachment(String key, Object value) { return setObjectAttachment(key, value); } @Experimental(""Experiment api for supporting Object transmission"") public RpcContext setObjectAttachment(String key, Object value) { // TODO compatible with previous CLIENT_ATTACHMENT.get().setObjectAttachment(key, value); return this; } /** * remove attachment. * * @param key * @return context */ public RpcContext removeAttachment(String key) { CLIENT_ATTACHMENT.get().removeAttachment(key); return this; } /** * get attachments. * * @return attachments */ @Deprecated public Map getAttachments() { return new AttachmentsAdapter.ObjectToStringMap(this.getObjectAttachments()); } /** * get attachments. * * @return attachments */ @Experimental(""Experiment api for supporting Object transmission"") public Map getObjectAttachments() { Map result = new HashMap<>((int) ((CLIENT_ATTACHMENT.get().attachments.size() + SERVER_ATTACHMENT.get().attachments.size()) / .75) + 1); result.putAll(SERVER_ATTACHMENT.get().attachments); result.putAll(CLIENT_ATTACHMENT.get().attachments); return result; } /** * set attachments * * @param attachment * @return context */ public RpcContext setAttachments(Map attachment) { CLIENT_ATTACHMENT.get().attachments.clear(); if (attachment != null && attachment.size() > 0) { CLIENT_ATTACHMENT.get().attachments.putAll(attachment); } return this; } /** * set attachments * * @param attachment * @return context */ @Experimental(""Experiment api for supporting Object transmission"") public RpcContext setObjectAttachments(Map attachment) { CLIENT_ATTACHMENT.get().attachments.clear(); if (attachment != null && attachment.size() > 0) { CLIENT_ATTACHMENT.get().attachments.putAll(attachment); } return this; } public void clearAttachments() { CLIENT_ATTACHMENT.get().attachments.clear(); } /** * get values. * * @return values */ @Deprecated public Map get() { return CLIENT_ATTACHMENT.get().get(); } /** * set value. * * @param key * @param value * @return context */ @Deprecated public RpcContext set(String key, Object value) { CLIENT_ATTACHMENT.get().set(key, value); return this; } /** * remove value. * * @param key * @return value */ @Deprecated public RpcContext remove(String key) { CLIENT_ATTACHMENT.get().remove(key); return this; } /** * get value. * * @param key * @return value */ @Deprecated public Object get(String key) { return CLIENT_ATTACHMENT.get().get(key); } /** * @deprecated Replace to isProviderSide() */ @Deprecated public boolean isServerSide() { return SERVICE_CONTEXT.get().isServerSide(); } /** * @deprecated Replace to isConsumerSide() */ @Deprecated public boolean isClientSide() { return SERVICE_CONTEXT.get().isClientSide(); } /** * @deprecated Replace to getUrls() */ @Deprecated @SuppressWarnings({""unchecked"", ""rawtypes""}) public List> getInvokers() { return SERVICE_CONTEXT.get().getInvokers(); } public RpcContext setInvokers(List> invokers) { return SERVICE_CONTEXT.get().setInvokers(invokers); } /** * @deprecated Replace to getUrl() */ @Deprecated public Invoker getInvoker() { return SERVICE_CONTEXT.get().getInvoker(); } public RpcContext setInvoker(Invoker invoker) { return SERVICE_CONTEXT.get().setInvoker(invoker); } /** * @deprecated Replace to getMethodName(), getParameterTypes(), getArguments() */ @Deprecated public Invocation getInvocation() { return SERVICE_CONTEXT.get().getInvocation(); } public RpcContext setInvocation(Invocation invocation) { return SERVICE_CONTEXT.get().setInvocation(invocation); } /** * Async invocation. Timeout will be handled even if Future.get() is not called. * * @param callable * @return get the return result from future.get() */ @SuppressWarnings(""unchecked"") public CompletableFuture asyncCall(Callable callable) { return SERVICE_CONTEXT.get().asyncCall(callable); } /** * one way async call, send request only, and result is not required * * @param runnable */ public void asyncCall(Runnable runnable) { SERVICE_CONTEXT.get().asyncCall(runnable); } /** * @return * @throws IllegalStateException */ @SuppressWarnings(""unchecked"") public static AsyncContext startAsync() throws IllegalStateException { return RpcContextAttachment.startAsync(); } protected void setAsyncContext(AsyncContext asyncContext) { SERVER_ATTACHMENT.get().setAsyncContext(asyncContext); } public boolean isAsyncStarted() { return SERVER_ATTACHMENT.get().isAsyncStarted(); } public boolean stopAsync() { return SERVER_ATTACHMENT.get().stopAsync(); } public AsyncContext getAsyncContext() { return SERVER_ATTACHMENT.get().getAsyncContext(); } public String getGroup() { return SERVICE_CONTEXT.get().getGroup(); } public String getVersion() { return SERVICE_CONTEXT.get().getVersion(); } public String getInterfaceName() { return SERVICE_CONTEXT.get().getInterfaceName(); } public String getProtocol() { return SERVICE_CONTEXT.get().getProtocol(); } public String getServiceKey() { return SERVICE_CONTEXT.get().getServiceKey(); } public String getProtocolServiceKey() { return SERVICE_CONTEXT.get().getProtocolServiceKey(); } public URL getConsumerUrl() { return SERVICE_CONTEXT.get().getConsumerUrl(); } public void setConsumerUrl(URL consumerUrl) { SERVICE_CONTEXT.get().setConsumerUrl(consumerUrl); } @Deprecated public static void setRpcContext(URL url) { RpcServiceContext.getServiceContext().setConsumerUrl(url); } protected static RestoreContext clearAndStoreContext() { RestoreContext restoreContext = new RestoreContext(); RpcContext.removeContext(); return restoreContext; } protected static RestoreContext storeContext() { return new RestoreContext(); } public static RestoreServiceContext storeServiceContext() { return new RestoreServiceContext(); } public static void restoreServiceContext(RestoreServiceContext restoreServiceContext) { if (restoreServiceContext != null) { restoreServiceContext.restore(); } } protected static void restoreContext(RestoreContext restoreContext) { if (restoreContext != null) { restoreContext.restore(); } } /** * Used to temporarily store and restore all kinds of contexts of current thread. */ public static class RestoreContext { private final RpcServiceContext serviceContext; private final RpcContextAttachment clientAttachment; private final RpcContextAttachment serverAttachment; private final RpcContextAttachment clientResponseLocal; private final RpcContextAttachment serverResponseLocal; public RestoreContext() { serviceContext = getServiceContext().copyOf(false); clientAttachment = getClientAttachment().copyOf(false); serverAttachment = getServerAttachment().copyOf(false); clientResponseLocal = getClientResponseContext().copyOf(false); serverResponseLocal = getServerResponseContext().copyOf(false); } public void restore() { if (serviceContext != null) { SERVICE_CONTEXT.set(serviceContext); } else { removeServiceContext(); } if (clientAttachment != null) { CLIENT_ATTACHMENT.set(clientAttachment); } else { removeClientAttachment(); } if (serverAttachment != null) { SERVER_ATTACHMENT.set(serverAttachment); } else { removeServerAttachment(); } if (clientResponseLocal != null) { CLIENT_RESPONSE_LOCAL.set(clientResponseLocal); } else { removeClientResponseContext(); } if (serverResponseLocal != null) { SERVER_RESPONSE_LOCAL.set(serverResponseLocal); } else { removeServerResponseContext(); } } } public static class RestoreServiceContext { private final RpcServiceContext serviceContext; public RestoreServiceContext() { RpcServiceContext originContext = getCurrentServiceContext(); if (originContext == null) { this.serviceContext = null; } else { this.serviceContext = originContext.copyOf(true); } } protected void restore() { if (serviceContext != null) { SERVICE_CONTEXT.set(serviceContext); } else { removeServiceContext(); } } } } ","response " "/* * Copyright 2013 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.spdy; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.SuppressJava6Requirement; import java.util.zip.Deflater; import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; import static io.netty.util.internal.ObjectUtil.checkNotNullWithIAE; class SpdyHeaderBlockZlibEncoder extends SpdyHeaderBlockRawEncoder { private final Deflater compressor; private boolean finished; SpdyHeaderBlockZlibEncoder(SpdyVersion spdyVersion, int compressionLevel) { super(spdyVersion); if (compressionLevel < 0 || compressionLevel > 9) { throw new IllegalArgumentException( ""compressionLevel: "" + compressionLevel + "" (expected: 0-9)""); } compressor = new Deflater(compressionLevel); compressor.setDictionary(SPDY_DICT); } private int setInput(ByteBuf de [MASK] ) { int len = de [MASK] .readableBytes(); if (de [MASK] .hasArray()) { compressor.setInput(de [MASK] .array(), de [MASK] .arrayOffset() + de [MASK] .readerIndex(), len); } else { byte[] in = new byte[len]; de [MASK] .getBytes(de [MASK] .readerIndex(), in); compressor.setInput(in, 0, in.length); } return len; } private ByteBuf encode(ByteBufAllocator alloc, int len) { ByteBuf [MASK] = alloc.heapBuffer(len); boolean release = true; try { while (compressInto( [MASK] )) { // Although unlikely, it's possible that the [MASK] size is larger than the de [MASK] size [MASK] .ensureWritable( [MASK] .capacity() << 1); } release = false; return [MASK] ; } finally { if (release) { [MASK] .release(); } } } @SuppressJava6Requirement(reason = ""Guarded by java version check"") private boolean compressInto(ByteBuf [MASK] ) { byte[] out = [MASK] .array(); int off = [MASK] .arrayOffset() + [MASK] .writerIndex(); int toWrite = [MASK] .writableBytes(); final int numBytes; if (PlatformDependent.javaVersion() >= 7) { numBytes = compressor.deflate(out, off, toWrite, Deflater.SYNC_FLUSH); } else { numBytes = compressor.deflate(out, off, toWrite); } [MASK] .writerIndex( [MASK] .writerIndex() + numBytes); return numBytes == toWrite; } @Override public ByteBuf encode(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception { checkNotNullWithIAE(alloc, ""alloc""); checkNotNullWithIAE(frame, ""frame""); if (finished) { return Unpooled.EMPTY_BUFFER; } ByteBuf de [MASK] = super.encode(alloc, frame); try { if (!de [MASK] .isReadable()) { return Unpooled.EMPTY_BUFFER; } int len = setInput(de [MASK] ); return encode(alloc, len); } finally { de [MASK] .release(); } } @Override public void end() { if (finished) { return; } finished = true; compressor.end(); super.end(); } } ","compressed " "/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package libcore.java.net; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.BindException; import java.net.ConnectException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketImpl; import java.net.URI; import java.net.UnknownHostException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /* J2ObjC removed: not supported by Junit 4.11 (https://github.com/google/j2objc/issues/1318). import libcore.junit.junit3.TestCaseWithRules; import libcore.junit.util.ResourceLeakageDetector; */ import junit.framework.TestCase; import org.junit.Rule; import org.junit.rules.TestRule; public class SocketTest extends TestCase /* J2ObjC removed: TestCaseWithRules */ { /* J2ObjC removed: not supported by Junit 4.11 (https://github.com/google/j2objc/issues/1318). @Rule public TestRule resourceLeakageDetectorRule = ResourceLeakageDetector.getRule(); */ // This hostname is required to resolve to 127.0.0.1 and ::1 for all tests to pass. private static final String ALL_LOOPBACK_HOSTNAME = ""loopback46.unittest.grpc.io""; // From net/inet_ecn.h private static final int INET_ECN_MASK = 0x3; // See http://b/2980559. public void test_close() throws Exception { Socket s = new Socket(); s.close(); // Closing a closed socket does nothing. s.close(); } /** * Our getLocalAddress and getLocalPort currently use getsockname(3). * This means they give incorrect results on closed sockets (as well * as requiring an unnecessary call into native code). */ public void test_getLocalAddress_after_close() throws Exception { Socket s = new Socket(); try { // Bind to an ephemeral local port. s.bind(new InetSocketAddress(""localhost"", 0)); assertTrue(s.getLocalAddress().toString(), s.getLocalAddress().isLoopbackAddress()); // What local port did we get? int localPort = s.getLocalPort(); assertTrue(localPort > 0); // Now close the socket... s.close(); // The RI returns the ANY address but the original local port after close. assertTrue(s.getLocalAddress().isAnyLocalAddress()); assertEquals(localPort, s.getLocalPort()); } finally { s.close(); } } // http://code.google.com/p/android/issues/detail?id=7935 public void test_newSocket_connection_refused() throws Exception { try { new Socket(""localhost"", 80); fail(""connection should have been refused""); } catch (ConnectException expected) { } } // http://code.google.com/p/android/issues/detail?id=3123 // http://code.google.com/p/android/issues/detail?id=1933 public void test_socketLocalAndRemoteAddresses() throws Exception { checkSocketLocalAndRemoteAddresses(false); checkSocketLocalAndRemoteAddresses(true); } public void checkSocketLocalAndRemoteAddresses(boolean [MASK] ) throws Exception { InetAddress host = InetAddress.getLocalHost(); // Open a local server port. ServerSocketChannel ssc = ServerSocketChannel.open(); InetSocketAddress listenAddr = new InetSocketAddress(host, 0); try { ssc.socket().bind(listenAddr, 0); } catch (BindException e) { // Continuous build environment doesn't support localhost sockets. return; } ServerSocket ss = ssc.socket(); // Open a socket to the local port. SocketChannel out = SocketChannel.open(); out.configureBlocking(false); if ( [MASK] ) { out.socket().setTcpNoDelay(false); } InetSocketAddress addr = new InetSocketAddress(host, ssc.socket().getLocalPort()); out.connect(addr); while (!out.finishConnect()) { Thread.sleep(1); } SocketChannel in = ssc.accept(); if ( [MASK] ) { in.socket().setTcpNoDelay(false); } InetSocketAddress listenAddress = (InetSocketAddress) in.socket().getLocalSocketAddress(); InetSocketAddress outRemoteAddress = (InetSocketAddress) out.socket().getRemoteSocketAddress(); InetSocketAddress outLocalAddress = (InetSocketAddress) out.socket().getLocalSocketAddress(); InetSocketAddress inLocalAddress = (InetSocketAddress) in.socket().getLocalSocketAddress(); InetSocketAddress inRemoteAddress = (InetSocketAddress) in.socket().getRemoteSocketAddress(); /* J2ObjC removed. System.err.println(""listenAddress: "" + listenAddr); System.err.println(""inLocalAddress: "" + inLocalAddress); System.err.println(""inRemoteAddress: "" + inRemoteAddress); System.err.println(""outLocalAddress: "" + outLocalAddress); System.err.println(""outRemoteAddress: "" + outRemoteAddress); */ assertEquals(outRemoteAddress.getPort(), ss.getLocalPort()); assertEquals(inLocalAddress.getPort(), ss.getLocalPort()); assertEquals(inRemoteAddress.getPort(), outLocalAddress.getPort()); assertEquals(inLocalAddress.getAddress(), ss.getInetAddress()); assertEquals(inRemoteAddress.getAddress(), ss.getInetAddress()); assertEquals(outLocalAddress.getAddress(), ss.getInetAddress()); assertEquals(outRemoteAddress.getAddress(), ss.getInetAddress()); assertFalse(ssc.socket().isClosed()); assertTrue(ssc.socket().isBound()); assertTrue(in.isConnected()); assertTrue(in.socket().isConnected()); assertTrue(out.socket().isConnected()); assertTrue(out.isConnected()); in.close(); out.close(); ssc.close(); assertTrue(ssc.socket().isClosed()); assertTrue(ssc.socket().isBound()); assertFalse(in.isConnected()); assertFalse(in.socket().isConnected()); assertFalse(out.socket().isConnected()); assertFalse(out.isConnected()); assertNull(in.socket().getRemoteSocketAddress()); assertNull(out.socket().getRemoteSocketAddress()); // As per docs and RI - server socket local address methods continue to return the bind() // addresses even after close(). assertEquals(listenAddress, ssc.socket().getLocalSocketAddress()); // As per docs and RI - socket local address methods return the wildcard address before // bind() and after close(), but the port will be the same as it was before close(). InetSocketAddress inLocalAddressAfterClose = (InetSocketAddress) in.socket().getLocalSocketAddress(); assertTrue(inLocalAddressAfterClose.getAddress().isAnyLocalAddress()); assertEquals(inLocalAddress.getPort(), inLocalAddressAfterClose.getPort()); InetSocketAddress outLocalAddressAfterClose = (InetSocketAddress) out.socket().getLocalSocketAddress(); assertTrue(outLocalAddressAfterClose.getAddress().isAnyLocalAddress()); assertEquals(outLocalAddress.getPort(), outLocalAddressAfterClose.getPort()); } // SocketOptions.setOption has weird behavior for setSoLinger/SO_LINGER. // This test ensures we do what the RI does. public void test_SocketOptions_setOption() throws Exception { class MySocketImpl extends SocketImpl { public int option; public Object value; public boolean createCalled; public boolean createStream; public MySocketImpl() { super(); } @Override protected void accept(SocketImpl arg0) throws IOException { } @Override protected int available() throws IOException { return 0; } @Override protected void bind(InetAddress arg0, int arg1) throws IOException { } @Override protected void close() throws IOException { } @Override protected void connect(String arg0, int arg1) throws IOException { } @Override protected void connect(InetAddress arg0, int arg1) throws IOException { } @Override protected void connect(SocketAddress arg0, int arg1) throws IOException { } @Override protected InputStream getInputStream() throws IOException { return null; } @Override protected OutputStream getOutputStream() throws IOException { return null; } @Override protected void listen(int arg0) throws IOException { } @Override protected void sendUrgentData(int arg0) throws IOException { } public Object getOption(int arg0) throws SocketException { return null; } @Override protected void create(boolean isStream) throws IOException { this.createCalled = true; this.createStream = isStream; } public void setOption(int option, Object value) throws SocketException { this.option = option; this.value = value; } } class MySocket extends Socket { public MySocket(SocketImpl impl) throws SocketException { super(impl); } } MySocketImpl impl = new MySocketImpl(); Socket s = new MySocket(impl); // Check that, as per the SocketOptions.setOption documentation, we pass false rather // than -1 to the SocketImpl when setSoLinger is called with the first argument false. s.setSoLinger(false, -1); assertEquals(Boolean.FALSE, (Boolean) impl.value); // We also check that SocketImpl.create was called. SocketChannelImpl.SocketAdapter // subclasses Socket, and whether or not to call SocketImpl.create is the main behavioral // difference. assertEquals(true, impl.createCalled); s.setSoLinger(false, 0); assertEquals(Boolean.FALSE, (Boolean) impl.value); s.setSoLinger(false, 1); assertEquals(Boolean.FALSE, (Boolean) impl.value); // Check that otherwise, we pass down an Integer. s.setSoLinger(true, 0); assertEquals(Integer.valueOf(0), (Integer) impl.value); s.setSoLinger(true, 1); assertEquals(Integer.valueOf(1), (Integer) impl.value); } public void test_setTrafficClass() throws Exception { try (Socket s = new Socket()) { for (int i = 0; i <= 255; ++i) { s.setTrafficClass(i); // b/30909505 // Linux does not set ECN bits for IP_TOS, but sets for IPV6_TCLASS. We should // accept either output. int actual = s.getTrafficClass(); assertTrue(i == actual || // IPV6_TCLASS (actual == (i & ~INET_ECN_MASK))); // IP_TOS: ECN bits should be 0 } } } public void testReadAfterClose() throws Exception { MockServer server = new MockServer(); server.enqueue(new byte[]{5, 3}, 0); Socket socket = new Socket(""localhost"", server.port); InputStream in = socket.getInputStream(); assertEquals(5, in.read()); assertEquals(3, in.read()); assertEquals(-1, in.read()); assertEquals(-1, in.read()); socket.close(); in.close(); /* * Rather astonishingly, read() doesn't throw even though the stream is * closed. This is consistent with the RI's behavior. */ assertEquals(-1, in.read()); assertEquals(-1, in.read()); server.shutdown(); } public void testWriteAfterClose() throws Exception { MockServer server = new MockServer(); server.enqueue(new byte[0], 3); Socket socket = new Socket(""localhost"", server.port); OutputStream out = socket.getOutputStream(); out.write(5); out.write(3); socket.close(); out.close(); try { out.write(9); fail(); } catch (IOException expected) { } server.shutdown(); } // http://b/5534202 public void testAvailable() throws Exception { for (int i = 0; i < 100; i++) { assertAvailableReturnsZeroAfterSocketReadsAllData(); /* J2ObjC removed. System.out.println(""Success on rep "" + i); */ } } private void assertAvailableReturnsZeroAfterSocketReadsAllData() throws Exception { final byte[] data = ""foo"".getBytes(); final ServerSocket serverSocket = new ServerSocket(0); new Thread() { @Override public void run() { try { Socket socket = serverSocket.accept(); socket.getOutputStream().write(data); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }.start(); Socket socket = new Socket(""localhost"", serverSocket.getLocalPort()); byte[] readBuffer = new byte[128]; InputStream in = socket.getInputStream(); int total = 0; // to prevent available() from cheating after EOF, stop reading before -1 is returned while (total < data.length) { total += in.read(readBuffer); } assertEquals(0, in.available()); socket.close(); serverSocket.close(); } public void testInitialState() throws Exception { Socket s = new Socket(); try { assertFalse(s.isBound()); assertFalse(s.isClosed()); assertFalse(s.isConnected()); assertEquals(-1, s.getLocalPort()); assertTrue(s.getLocalAddress().isAnyLocalAddress()); assertNull(s.getLocalSocketAddress()); assertNull(s.getInetAddress()); assertEquals(0, s.getPort()); assertNull(s.getRemoteSocketAddress()); assertFalse(s.getReuseAddress()); assertNull(s.getChannel()); } finally { s.close(); } } public void testStateAfterClose() throws Exception { Socket s = new Socket(); try { s.bind(new InetSocketAddress(Inet4Address.getLocalHost(), 0)); } catch (BindException e) { // Continuous build environment doesn't support localhost sockets. return; } InetSocketAddress boundAddress = (InetSocketAddress) s.getLocalSocketAddress(); s.close(); assertTrue(s.isBound()); assertTrue(s.isClosed()); assertFalse(s.isConnected()); assertTrue(s.getLocalAddress().isAnyLocalAddress()); assertEquals(boundAddress.getPort(), s.getLocalPort()); InetSocketAddress localAddressAfterClose = (InetSocketAddress) s.getLocalSocketAddress(); assertTrue(localAddressAfterClose.getAddress().isAnyLocalAddress()); assertEquals(boundAddress.getPort(), localAddressAfterClose.getPort()); } // b/282199142 // public void testCloseDuringConnect() throws Exception { // final CountDownLatch signal = new CountDownLatch(1); // final Socket s = new Socket(); // new Thread() { // @Override // public void run() { // try { // // This address is reserved for documentation: should never be reachable. // InetSocketAddress unreachableIp = new InetSocketAddress(""192.0.2.0"", 80); // // This should never return. // s.connect(unreachableIp, 0 /* infinite */); // fail(""Connect returned unexpectedly for: "" + unreachableIp); // } catch (SocketException expected) { // /* TODO(zgao): fix NET_ThrowByNameWithLastError() and enable. // assertTrue(expected.getMessage().contains(""Socket closed"")); // */ // assertTrue(expected.getMessage().contains(""connect failed"")); // signal.countDown(); // } catch (IOException e) { // fail(""Unexpected exception: "" + e); // } // } // }.start(); // // Wait for the connect() thread to run and start connect() // Thread.sleep(2000); // s.close(); // boolean connectUnblocked = signal.await(2000, TimeUnit.MILLISECONDS); // assertTrue(connectUnblocked); // } // http://b/29092095 public void testSocketWithProxySet() throws Exception { ProxySelector ps = ProxySelector.getDefault(); try { ProxySelector.setDefault(new ProxySelector() { @Override public List select(URI uri) { fail(""ProxySelector#select was called""); return null; // unreachable. } @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { fail(""ProxySelector#connectFail was called""); } }); ServerSocket server = new ServerSocket(0); Socket client = new Socket(InetAddress.getLocalHost(), server.getLocalPort()); client.close(); server.close(); } finally { ProxySelector.setDefault(ps); } } // b/25805791 + b/26470377 public void testFileDescriptorStaysSame() throws Exception { // SocketImplementation FileDescriptor object shouldn't change after calling // bind (and many other methods). Socket s = new Socket(); // There's an assumption that newly created // socket has a non-null file-descriptor (b/26169052, b/26084000) FileDescriptor fd1 = s.getFileDescriptor$(); assertNotNull(fd1); int fd1Val = fd1.getInt$(); assertEquals(-1, fd1Val); s.bind(new InetSocketAddress(InetAddress.getByName(""0.0.0.0""), 0)); FileDescriptor fd2 = s.getFileDescriptor$(); assertSame(fd1, fd2); int fd2Val = fd2.getInt$(); assertTrue(fd1Val != fd2Val); // The actual fd should be different s.close(); FileDescriptor fd3 = s.getFileDescriptor$(); assertSame(fd1, fd3); assertFalse(fd3.valid()); } static class MockServer { private ExecutorService executor; private ServerSocket serverSocket; private int port = -1; MockServer() throws IOException { executor = Executors.newCachedThreadPool(); serverSocket = new ServerSocket(0); serverSocket.setReuseAddress(true); port = serverSocket.getLocalPort(); } public Future enqueue(final byte[] sendBytes, final int receiveByteCount) throws IOException { return executor.submit(new Callable() { @Override public byte[] call() throws Exception { Socket socket = serverSocket.accept(); OutputStream out = socket.getOutputStream(); out.write(sendBytes); InputStream in = socket.getInputStream(); byte[] result = new byte[receiveByteCount]; int total = 0; while (total < receiveByteCount) { total += in.read(result, total, result.length - total); } socket.close(); return result; } }); } public void shutdown() throws IOException { serverSocket.close(); executor.shutdown(); } } // b/26354315 public void testDoNotCallCloseFromSocketCtor() { // Original openJdk7 Socket implementation may call Socket#close() inside a constructor. // In this case, classes that extend Socket wont be fully constructed when they // receive #close() call. This test makes sure this won't happen // Extend Socket class SocketThatFailOnClose extends Socket { public SocketThatFailOnClose(String host, int port) throws UnknownHostException, IOException { super(host, port); } public SocketThatFailOnClose(InetAddress address, int port) throws IOException { super(address, port); } public SocketThatFailOnClose(String host, int port, InetAddress localAddr, int localPort) throws IOException { super(host, port, localAddr, localPort); } public SocketThatFailOnClose(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException { super(address, port, localAddr, localPort); } public SocketThatFailOnClose(String host, int port, boolean stream) throws IOException { super(host, port, stream); } public SocketThatFailOnClose(InetAddress host, int port, boolean stream) throws IOException { super(host, port, stream); } @Override public void close() { fail(""Do not call close from the Socket constructor""); } } // Test all Socket ctors try { new SocketThatFailOnClose(""localhost"", 1); fail(); } catch(IOException expected) {} try { new SocketThatFailOnClose(InetAddress.getLocalHost(), 1); fail(); } catch(IOException expected) {} try { new SocketThatFailOnClose(""localhost"", 1, null, 0); fail(); } catch(IOException expected) {} try { new SocketThatFailOnClose(InetAddress.getLocalHost(), 1, null, 0); fail(); } catch(IOException expected) {} try { new SocketThatFailOnClose(""localhost"", 1, true); fail(); } catch(IOException expected) {} try { new SocketThatFailOnClose(InetAddress.getLocalHost(), 1, true); fail(); } catch(IOException expected) {} } // b/30007735 /* J2ObjC removed. public void testSocketTestAllAddresses() throws Exception { // Socket Ctor should try all sockets. // // This test creates a server socket bound to 127.0.0.1 or ::1 only, and connects using a // hostname that resolves to both addresses. We should be able to connect to the server // socket in either setup. final String loopbackHost = ALL_LOOPBACK_HOSTNAME; assertTrue(""Loopback DNS record is unreachable or is invalid."", checkLoopbackHost( loopbackHost)); final int port = 9999; for (InetAddress addr : new InetAddress[]{ Inet4Address.LOOPBACK, Inet6Address.LOOPBACK }) { try (ServerSocket ss = new ServerSocket(port, 0, addr)) { new Thread(() -> { try { ss.accept(); } catch (IOException e) { e.printStackTrace(); } }).start(); assertTrue(canConnect(loopbackHost, port)); } } } */ /** Confirm the supplied hostname maps to only loopback addresses. */ private static boolean checkLoopbackHost(String host) { try { List addrs = Arrays.asList(InetAddress.getAllByName(host)); return addrs.stream().allMatch(InetAddress::isLoopbackAddress) && addrs.contains(Inet4Address.LOOPBACK) && addrs.contains(Inet6Address.LOOPBACK); } catch (UnknownHostException e) { return false; } } private static boolean canConnect(String host, int port) { try(Socket sock = new Socket(host, port)) { return sock.isConnected(); } catch (IOException e) { return false; } } } ","setOptions " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.ui; import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; import static com.google.android.exoplayer2.util.Util.getDrawable; import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.opengl.GLSurfaceView; import android.os.Looper; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.content.ContextCompat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; import com.google.common.collect.ImmutableList; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * A high level view for {@link Player} media playbacks. It displays video, subtitles and album art * during playback, and displays playback controls using a {@link PlayerControlView}. * *

A PlayerView can be customized by setting attributes (or calling corresponding methods), * overriding drawables, overriding the view's layout file, or by specifying a custom view layout * file. * *

Attributes

* * The following attributes can be set on a PlayerView when used in a layout XML file: * *
    *
  • {@code use_artwork} - Whether artwork is used if available in audio streams. *
      *
    • Corresponding method: {@link #setUseArtwork(boolean)} *
    • Default: {@code true} *
    *
  • {@code default_artwork} - Default artwork to use if no artwork available in audio * streams. *
      *
    • Corresponding method: {@link #setDefaultArtwork(Drawable)} *
    • Default: {@code null} *
    *
  • {@code use_controller} - Whether the playback controls can be shown. *
      *
    • Corresponding method: {@link #setUseController(boolean)} *
    • Default: {@code true} *
    *
  • {@code hide_on_touch} - Whether the playback controls are hidden by touch events. *
      *
    • Corresponding method: {@link #setControllerHideOnTouch(boolean)} *
    • Default: {@code true} *
    *
  • {@code auto_show} - Whether the playback controls are automatically shown when * playback starts, pauses, ends, or fails. If set to false, the playback controls can be * manually operated with {@link #showController()} and {@link #hideController()}. *
      *
    • Corresponding method: {@link #setControllerAutoShow(boolean)} *
    • Default: {@code true} *
    *
  • {@code hide_during_ads} - Whether the playback controls are hidden during ads. * Controls are always shown during ads if they are enabled and the player is paused. *
      *
    • Corresponding method: {@link #setControllerHideDuringAds(boolean)} *
    • Default: {@code true} *
    *
  • {@code show_buffering} - Whether the buffering spinner is displayed when the player * is buffering. Valid values are {@code never}, {@code when_playing} and {@code always}. *
      *
    • Corresponding method: {@link #setShowBuffering(int)} *
    • Default: {@code never} *
    *
  • {@code resize_mode} - Controls how video and album art is resized within the view. * Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height}, {@code fill} and * {@code zoom}. *
      *
    • Corresponding method: {@link #setResizeMode(int)} *
    • Default: {@code fit} *
    *
  • {@code surface_type} - The type of surface view used for video playbacks. Valid * values are {@code surface_view}, {@code texture_view}, {@code spherical_gl_surface_view}, * {@code video_decoder_gl_surface_view} and {@code none}. Using {@code none} is recommended * for audio only applications, since creating the surface can be expensive. Using {@code * surface_view} is recommended for video applications. Note, TextureView can only be used in * a hardware accelerated window. When rendered in software, TextureView will draw nothing. *
      *
    • Corresponding method: None *
    • Default: {@code surface_view} *
    *
  • {@code shutter_background_color} - The background color of the {@code exo_shutter} * view. *
      *
    • Corresponding method: {@link #setShutterBackgroundColor(int)} *
    • Default: {@code unset} *
    *
  • {@code keep_content_on_player_reset} - Whether the currently displayed video frame * or media artwork is kept visible when the player is reset. *
      *
    • Corresponding method: {@link #setKeepContentOnPlayerReset(boolean)} *
    • Default: {@code false} *
    *
  • {@code player_layout_id} - Specifies the id of the layout to be inflated. See below * for more details. *
      *
    • Corresponding method: None *
    • Default: {@code R.layout.exo_player_view} *
    *
  • {@code controller_layout_id} - Specifies the id of the layout resource to be * inflated by the child {@link PlayerControlView}. See below for more details. *
      *
    • Corresponding method: None *
    • Default: {@code R.layout.exo_player_control_view} *
    *
  • All attributes that can be set on {@link PlayerControlView} and {@link DefaultTimeBar} can * also be set on a PlayerView, and will be propagated to the inflated {@link * PlayerControlView} unless the layout is overridden to specify a custom {@code * exo_controller} (see below). *
* *

Overriding drawables

* * The drawables used by {@link PlayerControlView} (with its default layout file) can be overridden * by drawables with the same names defined in your application. See the {@link PlayerControlView} * documentation for a list of drawables that can be overridden. * *

Overriding the layout file

* * To customize the layout of PlayerView throughout your app, or just for certain configurations, * you can define {@code exo_player_view.xml} layout files in your application {@code res/layout*} * directories. These layouts will override the one provided by the library, and will be inflated * for use by PlayerView. The view identifies and binds its children by looking for the following * ids: * *
    *
  • {@code exo_content_frame} - A frame whose aspect ratio is resized based on the video * or album art of the media being played, and the configured {@code resize_mode}. The video * surface view is inflated into this frame as its first child. *
      *
    • Type: {@link AspectRatioFrameLayout} *
    *
  • {@code exo_shutter} - A view that's made visible when video should be hidden. This * view is typically an opaque view that covers the video surface, thereby obscuring it when * visible. Obscuring the surface in this way also helps to prevent flicker at the start of * playback when {@code surface_type=""surface_view""}. *
      *
    • Type: {@link View} *
    *
  • {@code exo_buffering} - A view that's made visible when the player is buffering. * This view typically displays a buffering spinner or animation. *
      *
    • Type: {@link View} *
    *
  • {@code exo_subtitles} - Displays subtitles. *
      *
    • Type: {@link SubtitleView} *
    *
  • {@code exo_artwork} - Displays album art. *
      *
    • Type: {@link ImageView} *
    *
  • {@code exo_error_message} - Displays an error message to the user if playback fails. *
      *
    • Type: {@link TextView} *
    *
  • {@code exo_controller_placeholder} - A placeholder that's replaced with the inflated * {@link PlayerControlView}. Ignored if an {@code exo_controller} view exists. *
      *
    • Type: {@link View} *
    *
  • {@code exo_controller} - An already inflated {@link PlayerControlView}. Allows use * of a custom extension of {@link PlayerControlView}. {@link PlayerControlView} and {@link * DefaultTimeBar} attributes set on the PlayerView will not be automatically propagated * through to this instance. If a view exists with this id, any {@code * exo_controller_placeholder} view will be ignored. *
      *
    • Type: {@link PlayerControlView} *
    *
  • {@code exo_ad_overlay} - A {@link FrameLayout} positioned on top of the player which * is used to show ad UI (if applicable). *
      *
    • Type: {@link FrameLayout} *
    *
  • {@code exo_overlay} - A {@link FrameLayout} positioned on top of the player which * the app can access via {@link #getOverlayFrameLayout()}, provided for convenience. *
      *
    • Type: {@link FrameLayout} *
    *
* *

All child views are optional and so can be omitted if not required, however where defined they * must be of the expected type. * *

Specifying a custom layout file

* * Defining your own {@code exo_player_view.xml} is useful to customize the layout of PlayerView * throughout your application. It's also possible to customize the layout for a single instance in * a layout file. This is achieved by setting the {@code player_layout_id} attribute on a * PlayerView. This will cause the specified layout to be inflated instead of {@code * exo_player_view.xml} for only the instance on which the attribute is set. * * @deprecated Use {@link StyledPlayerView} instead. */ @Deprecated public class PlayerView extends FrameLayout implements AdViewProvider { /** * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}. */ @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS}) public @interface ShowBuffering {} /** The buffering view is never shown. */ public static final int SHOW_BUFFERING_NEVER = 0; /** * The buffering view is shown when the player is in the {@link Player#STATE_BUFFERING buffering} * state and {@link Player#getPlayWhenReady() playWhenReady} is {@code true}. */ public static final int SHOW_BUFFERING_WHEN_PLAYING = 1; /** * The buffering view is always shown when the player is in the {@link Player#STATE_BUFFERING * buffering} state. */ public static final int SHOW_BUFFERING_ALWAYS = 2; private static final int SURFACE_TYPE_NONE = 0; private static final int SURFACE_TYPE_SURFACE_VIEW = 1; private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; private static final int SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW = 3; private static final int SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW = 4; private final ComponentListener componentListener; @Nullable private final AspectRatioFrameLayout contentFrame; @Nullable private final View shutterView; @Nullable private final View surfaceView; private final boolean surfaceViewIgnoresVideoAspectRatio; @Nullable private final ImageView artworkView; @Nullable private final SubtitleView subtitleView; @Nullable private final View bufferingView; @Nullable private final TextView errorMessageView; @Nullable private final PlayerControlView controller; @Nullable private final FrameLayout adOverlayFrameLayout; @Nullable private final FrameLayout overlayFrameLayout; @Nullable private Player player; private boolean useController; @Nullable private PlayerControlView.VisibilityListener controllerVisibilityListener; private boolean useArtwork; @Nullable private Drawable defaultArtwork; private @ShowBuffering int showBuffering; private boolean keepContentOnPlayerReset; @Nullable private ErrorMessageProvider errorMessageProvider; @Nullable private CharSequence customErrorMessage; private int controllerShowTimeoutMs; private boolean controllerAutoShow; private boolean controllerHideDuringAds; private boolean controllerHideOnTouch; private int textureViewRotation; private boolean isTouching; public PlayerView(Context context) { this(context, /* attrs= */ null); } public PlayerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, /* defStyleAttr= */ 0); } @SuppressWarnings({""nullness:argument"", ""nullness:method.invocation""}) public PlayerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); componentListener = new ComponentListener(); if (isInEditMode()) { contentFrame = null; shutterView = null; surfaceView = null; surfaceViewIgnoresVideoAspectRatio = false; artworkView = null; subtitleView = null; bufferingView = null; errorMessageView = null; controller = null; adOverlayFrameLayout = null; overlayFrameLayout = null; ImageView logo = new ImageView(context); if (Util.SDK_INT >= 23) { configureEditModeLogoV23(context, getResources(), logo); } else { configureEditModeLogo(context, getResources(), logo); } addView(logo); return; } boolean shutterColorSet = false; int shutterColor = 0; int playerLayoutId = R.layout.exo_player_view; boolean useArtwork = true; int defaultArtworkId = 0; boolean useController = true; int surfaceType = SURFACE_TYPE_SURFACE_VIEW; int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; int controllerShowTimeoutMs = PlayerControlView.DEFAULT_SHOW_TIMEOUT_MS; boolean controllerHideOnTouch = true; boolean controllerAutoShow = true; boolean controllerHideDuringAds = true; int showBuffering = SHOW_BUFFERING_NEVER; if (attrs != null) { TypedArray a = context .getTheme() .obtainStyledAttributes( attrs, R.styleable.PlayerView, defStyleAttr, /* defStyleRes= */ 0); try { shutterColorSet = a.hasValue(R.styleable.PlayerView_shutter_background_color); shutterColor = a.getColor(R.styleable.PlayerView_shutter_background_color, shutterColor); playerLayoutId = a.getResourceId(R.styleable.PlayerView_player_layout_id, playerLayoutId); useArtwork = a.getBoolean(R.styleable.PlayerView_use_artwork, useArtwork); defaultArtworkId = a.getResourceId(R.styleable.PlayerView_default_artwork, defaultArtworkId); useController = a.getBoolean(R.styleable.PlayerView_use_controller, useController); surfaceType = a.getInt(R.styleable.PlayerView_surface_type, surfaceType); resizeMode = a.getInt(R.styleable.PlayerView_resize_mode, resizeMode); controllerShowTimeoutMs = a.getInt(R.styleable.PlayerView_show_timeout, controllerShowTimeoutMs); controllerHideOnTouch = a.getBoolean(R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch); controllerAutoShow = a.getBoolean(R.styleable.PlayerView_auto_show, controllerAutoShow); showBuffering = a.getInteger(R.styleable.PlayerView_show_buffering, showBuffering); keepContentOnPlayerReset = a.getBoolean( R.styleable.PlayerView_keep_content_on_player_reset, keepContentOnPlayerReset); controllerHideDuringAds = a.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds); } finally { a.recycle(); } } LayoutInflater.from(context).inflate(playerLayoutId, this); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); // Content frame. contentFrame = findViewById(R.id.exo_content_frame); if (contentFrame != null) { setResizeModeRaw(contentFrame, resizeMode); } // Shutter view. shutterView = findViewById(R.id.exo_shutter); if (shutterView != null && shutterColorSet) { shutterView.setBackgroundColor(shutterColor); } // Create a surface view and insert it into the content frame, if there is one. boolean surfaceViewIgnoresVideoAspectRatio = false; if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) { ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); switch (surfaceType) { case SURFACE_TYPE_TEXTURE_VIEW: surfaceView = new TextureView(context); break; case SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW: try { Class clazz = Class.forName( ""com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView""); surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context); } catch (Exception e) { throw new IllegalStateException( ""spherical_gl_surface_view requires an ExoPlayer dependency"", e); } surfaceViewIgnoresVideoAspectRatio = true; break; case SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW: try { Class clazz = Class.forName(""com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView""); surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context); } catch (Exception e) { throw new IllegalStateException( ""video_decoder_gl_surface_view requires an ExoPlayer dependency"", e); } break; default: surfaceView = new SurfaceView(context); break; } surfaceView.setLayoutParams(params); // We don't want surfaceView to be clickable separately to the PlayerView itself, but we // do want to register as an OnClickListener so that surfaceView implementations can propagate // click events up to the PlayerView by calling their own performClick method. surfaceView.setOnClickListener(componentListener); surfaceView.setClickable(false); contentFrame.addView(surfaceView, 0); } else { surfaceView = null; } this.surfaceViewIgnoresVideoAspectRatio = surfaceViewIgnoresVideoAspectRatio; // Ad overlay frame layout. adOverlayFrameLayout = findViewById(R.id.exo_ad_overlay); // Overlay frame layout. overlayFrameLayout = findViewById(R.id.exo_overlay); // Artwork view. artworkView = findViewById(R.id.exo_artwork); this.useArtwork = useArtwork && artworkView != null; if (defaultArtworkId != 0) { defaultArtwork = ContextCompat.getDrawable(getContext(), defaultArtworkId); } // Subtitle view. subtitleView = findViewById(R.id.exo_subtitles); if (subtitleView != null) { subtitleView.setUserDefaultStyle(); subtitleView.setUserDefaultTextSize(); } // Buffering view. bufferingView = findViewById(R.id.exo_buffering); if (bufferingView != null) { bufferingView.setVisibility(View.GONE); } this.showBuffering = showBuffering; // Error message view. errorMessageView = findViewById(R.id.exo_error_message); if (errorMessageView != null) { errorMessageView.setVisibility(View.GONE); } // Playback control view. PlayerControlView customController = findViewById(R.id.exo_controller); View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder); if (customController != null) { this.controller = customController; } else if (controllerPlaceholder != null) { // Propagate attrs as playbackAttrs so that PlayerControlView's custom attributes are // transferred, but standard attributes (e.g. background) are not. this.controller = new PlayerControlView(context, null, 0, attrs); controller.setId(R.id.exo_controller); controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); int controllerIndex = parent.indexOfChild(controllerPlaceholder); parent.removeView(controllerPlaceholder); parent.addView(controller, controllerIndex); } else { this.controller = null; } this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0; this.controllerHideOnTouch = controllerHideOnTouch; this.controllerAutoShow = controllerAutoShow; this.controllerHideDuringAds = controllerHideDuringAds; this.useController = useController && controller != null; if (controller != null) { controller.hide(); controller.addVisibilityListener(/* listener= */ componentListener); } if (useController) { setClickable(true); } updateContentDescription(); } /** * Switches the view targeted by a given {@link Player}. * * @param player The player whose target view is being switched. * @param oldPlayerView The old view to detach from the player. * @param newPlayerView The new view to attach to the player. */ public static void switchTargetView( Player player, @Nullable PlayerView oldPlayerView, @Nullable PlayerView newPlayerView) { if (oldPlayerView == newPlayerView) { return; } // We attach the new view before detaching the old one because this ordering allows the player // to swap directly from one surface to another, without transitioning through a state where no // surface is attached. This is significantly more efficient and achieves a more seamless // transition when using platform provided video decoders. if (newPlayerView != null) { newPlayerView.setPlayer(player); } if (oldPlayerView != null) { oldPlayerView.setPlayer(null); } } /** Returns the player currently set on this view, or null if no player is set. */ @Nullable public Player getPlayer() { return player; } /** * Sets the {@link Player} to use. * *

To transition a {@link Player} from targeting one view to another, it's recommended to use * {@link #switchTargetView(Player, PlayerView, PlayerView)} rather than this method. If you do * wish to use this method directly, be sure to attach the player to the new view before * calling {@code setPlayer(null)} to detach it from the old one. This ordering is significantly * more efficient and may allow for more seamless transitions. * * @param player The {@link Player} to use, or {@code null} to detach the current player. Only * players which are accessed on the main thread are supported ({@code * player.getApplicationLooper() == Looper.getMainLooper()}). */ public void setPlayer(@Nullable Player player) { Assertions.checkState(Looper.myLooper() == Looper.getMainLooper()); Assertions.checkArgument( player == null || player.getApplicationLooper() == Looper.getMainLooper()); if (this.player == player) { return; } @Nullable Player oldPlayer = this.player; if (oldPlayer != null) { oldPlayer.removeListener(componentListener); if (oldPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) { if (surfaceView instanceof TextureView) { oldPlayer.clearVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SurfaceView) { oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView); } } } if (subtitleView != null) { subtitleView.setCues(null); } this.player = player; if (useController()) { controller.setPlayer(player); } updateBuffering(); updateErrorMessage(); updateForCurrentTrackSelections(/* isNewPlayer= */ true); if (player != null) { if (player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) { if (surfaceView instanceof TextureView) { player.setVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SurfaceView) { player.setVideoSurfaceView((SurfaceView) surfaceView); } updateAspectRatio(); } if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) { subtitleView.setCues(player.getCurrentCues().cues); } player.addListener(componentListener); maybeShowController(false); } else { hideController(); } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); if (surfaceView instanceof SurfaceView) { // Work around https://github.com/google/ExoPlayer/issues/3160. surfaceView.setVisibility(visibility); } } /** * Sets the {@link ResizeMode}. * * @param resizeMode The {@link ResizeMode}. */ public void setResizeMode(@ResizeMode int resizeMode) { Assertions.checkStateNotNull(contentFrame); contentFrame.setResizeMode(resizeMode); } /** Returns the {@link ResizeMode}. */ public @ResizeMode int getResizeMode() { Assertions.checkStateNotNull(contentFrame); return contentFrame.getResizeMode(); } /** Returns whether artwork is displayed if present in the media. */ public boolean getUseArtwork() { return useArtwork; } /** * Sets whether artwork is displayed if present in the media. * * @param useArtwork Whether artwork is displayed. */ public void setUseArtwork(boolean useArtwork) { Assertions.checkState(!useArtwork || artworkView != null); if (this.useArtwork != useArtwork) { this.useArtwork = useArtwork; updateForCurrentTrackSelections(/* isNewPlayer= */ false); } } /** Returns the default artwork to display. */ @Nullable public Drawable getDefaultArtwork() { return defaultArtwork; } /** * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is * present in the media. * * @param defaultArtwork the default artwork to display */ public void setDefaultArtwork(@Nullable Drawable defaultArtwork) { if (this.defaultArtwork != defaultArtwork) { this.defaultArtwork = defaultArtwork; updateForCurrentTrackSelections(/* isNewPlayer= */ false); } } /** Returns whether the playback controls can be shown. */ public boolean getUseController() { return useController; } /** * Sets whether the playback controls can be shown. If set to {@code false} the playback controls * are never visible and are disconnected from the player. * *

This call will update whether the view is clickable. After the call, the view will be * clickable if playback controls can be shown or if the view has a registered click listener. * * @param useController Whether the playback controls can be shown. */ public void setUseController(boolean useController) { Assertions.checkState(!useController || controller != null); setClickable(useController || hasOnClickListeners()); if (this.useController == useController) { return; } this.useController = useController; if (useController()) { controller.setPlayer(player); } else if (controller != null) { controller.hide(); controller.setPlayer(/* player= */ null); } updateContentDescription(); } /** * Sets the background color of the {@code exo_shutter} view. * * @param color The background color. */ public void setShutterBackgroundColor(int color) { if (shutterView != null) { shutterView.setBackgroundColor(color); } } /** * Sets whether the currently displayed video frame or media artwork is kept visible when the * player is reset. A player reset is defined to mean the player being re-prepared with different * media, the player transitioning to unprepared media or an empty list of media items, or the * player being replaced or cleared by calling {@link #setPlayer(Player)}. * *

If enabled, the currently displayed video frame or media artwork will be kept visible until * the player set on the view has been successfully prepared with new media and loaded enough of * it to have determined the available tracks. Hence enabling this option allows transitioning * from playing one piece of media to another, or from using one player instance to another, * without clearing the view's content. * *

If disabled, the currently displayed video frame or media artwork will be hidden as soon as * the player is reset. Note that the video frame is hidden by making {@code exo_shutter} visible. * Hence the video frame will not be hidden if using a custom layout that omits this view. * * @param keepContentOnPlayerReset Whether the currently displayed video frame or media artwork is * kept visible when the player is reset. */ public void setKeepContentOnPlayerReset(boolean keepContentOnPlayerReset) { if (this.keepContentOnPlayerReset != keepContentOnPlayerReset) { this.keepContentOnPlayerReset = keepContentOnPlayerReset; updateForCurrentTrackSelections(/* isNewPlayer= */ false); } } /** * Sets whether a buffering spinner is displayed when the player is in the buffering state. The * buffering spinner is not displayed by default. * * @param showBuffering The mode that defines when the buffering spinner is displayed. One of * {@link #SHOW_BUFFERING_NEVER}, {@link #SHOW_BUFFERING_WHEN_PLAYING} and {@link * #SHOW_BUFFERING_ALWAYS}. */ public void setShowBuffering(@ShowBuffering int showBuffering) { if (this.showBuffering != showBuffering) { this.showBuffering = showBuffering; updateBuffering(); } } /** * Sets the optional {@link ErrorMessageProvider}. * * @param errorMessageProvider The error message provider. */ public void setErrorMessageProvider( @Nullable ErrorMessageProvider errorMessageProvider) { if (this.errorMessageProvider != errorMessageProvider) { this.errorMessageProvider = errorMessageProvider; updateErrorMessage(); } } /** * Sets a custom error message to be displayed by the view. The error message will be displayed * permanently, unless it is cleared by passing {@code null} to this method. * * @param message The message to display, or {@code null} to clear a previously set message. */ public void setCustomErrorMessage(@Nullable CharSequence message) { Assertions.checkState(errorMessageView != null); customErrorMessage = message; updateErrorMessage(); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (player != null && player.isPlayingAd()) { return super.dispatchKeyEvent(event); } boolean isDpadKey = isDpadKey(event.getKeyCode()); boolean handled = false; if (isDpadKey && useController() && !controller.isVisible()) { // Handle the key event by showing the controller. maybeShowController(true); handled = true; } else if (dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event)) { // The key event was handled as a media key or by the super class. We should also show the // controller, or extend its show timeout if already visible. maybeShowController(true); handled = true; } else if (isDpadKey && useController()) { // The key event wasn't handled, but we should extend the controller's show timeout. maybeShowController(true); } return handled; } /** * Called to process media key events. Any {@link KeyEvent} can be passed but only media key * events will be handled. Does nothing if playback controls are disabled. * * @param event A key event. * @return Whether the key event was handled. */ public boolean dispatchMediaKeyEvent(KeyEvent event) { return useController() && controller.dispatchMediaKeyEvent(event); } /** Returns whether the controller is currently visible. */ public boolean isControllerVisible() { return controller != null && controller.isVisible(); } /** * Shows the playback controls. Does nothing if playback controls are disabled. * *

The playback controls are automatically hidden during playback after {{@link * #getControllerShowTimeoutMs()}}. They are shown indefinitely when playback has not started yet, * is paused, has ended or failed. */ public void showController() { showController(shouldShowControllerIndefinitely()); } /** Hides the playback controls. Does nothing if playback controls are disabled. */ public void hideController() { if (controller != null) { controller.hide(); } } /** * Returns the playback controls timeout. The playback controls are automatically hidden after * this duration of time has elapsed without user input and with playback or buffering in * progress. * * @return The timeout in milliseconds. A non-positive value will cause the controller to remain * visible indefinitely. */ public int getControllerShowTimeoutMs() { return controllerShowTimeoutMs; } /** * Sets the playback controls timeout. The playback controls are automatically hidden after this * duration of time has elapsed without user input and with playback or buffering in progress. * * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause the * controller to remain visible indefinitely. */ public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) { Assertions.checkStateNotNull(controller); this.controllerShowTimeoutMs = controllerShowTimeoutMs; if (controller.isVisible()) { // Update the controller's timeout if necessary. showController(); } } /** Returns whether the playback controls are hidden by touch events. */ public boolean getControllerHideOnTouch() { return controllerHideOnTouch; } /** * Sets whether the playback controls are hidden by touch events. * * @param controllerHideOnTouch Whether the playback controls are hidden by touch events. */ public void setControllerHideOnTouch(boolean controllerHideOnTouch) { Assertions.checkStateNotNull(controller); this.controllerHideOnTouch = controllerHideOnTouch; updateContentDescription(); } /** * Returns whether the playback controls are automatically shown when playback starts, pauses, * ends, or fails. If set to false, the playback controls can be manually operated with {@link * #showController()} and {@link #hideController()}. */ public boolean getControllerAutoShow() { return controllerAutoShow; } /** * Sets whether the playback controls are automatically shown when playback starts, pauses, ends, * or fails. If set to false, the playback controls can be manually operated with {@link * #showController()} and {@link #hideController()}. * * @param controllerAutoShow Whether the playback controls are allowed to show automatically. */ public void setControllerAutoShow(boolean controllerAutoShow) { this.controllerAutoShow = controllerAutoShow; } /** * Sets whether the playback controls are hidden when ads are playing. Controls are always shown * during ads if they are enabled and the player is paused. * * @param controllerHideDuringAds Whether the playback controls are hidden when ads are playing. */ public void setControllerHideDuringAds(boolean controllerHideDuringAds) { this.controllerHideDuringAds = controllerHideDuringAds; } /** * Sets the {@link PlayerControlView.VisibilityListener}. * * @param listener The listener to be notified about visibility changes, or null to remove the * current listener. */ public void setControllerVisibilityListener( @Nullable PlayerControlView.VisibilityListener listener) { Assertions.checkStateNotNull(controller); if (this.controllerVisibilityListener == listener) { return; } if (this.controllerVisibilityListener != null) { controller.removeVisibilityListener(this.controllerVisibilityListener); } this.controllerVisibilityListener = listener; if (listener != null) { controller.addVisibilityListener(listener); } } /** * Sets whether the rewind button is shown. * * @param showRewindButton Whether the rewind button is shown. */ public void setShowRewindButton(boolean showRewindButton) { Assertions.checkStateNotNull(controller); controller.setShowRewindButton(showRewindButton); } /** * Sets whether the fast forward button is shown. * * @param showFastForwardButton Whether the fast forward button is shown. */ public void setShowFastForwardButton(boolean showFastForwardButton) { Assertions.checkStateNotNull(controller); controller.setShowFastForwardButton(showFastForwardButton); } /** * Sets whether the previous button is shown. * * @param showPreviousButton Whether the previous button is shown. */ public void setShowPreviousButton(boolean showPreviousButton) { Assertions.checkStateNotNull(controller); controller.setShowPreviousButton(showPreviousButton); } /** * Sets whether the next button is shown. * * @param showNextButton Whether the next button is shown. */ public void setShowNextButton(boolean showNextButton) { Assertions.checkStateNotNull(controller); controller.setShowNextButton(showNextButton); } /** * Sets which repeat toggle modes are enabled. * * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}. */ public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { Assertions.checkStateNotNull(controller); controller.setRepeatToggleModes(repeatToggleModes); } /** * Sets whether the shuffle button is shown. * * @param showShuffleButton Whether the shuffle button is shown. */ public void setShowShuffleButton(boolean showShuffleButton) { Assertions.checkStateNotNull(controller); controller.setShowShuffleButton(showShuffleButton); } /** * Sets whether the time bar should show all windows, as opposed to just the current one. * * @param showMultiWindowTimeBar Whether to show all windows. */ public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) { Assertions.checkStateNotNull(controller); controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar); } /** * Sets the millisecond positions of extra ad markers relative to the start of the window (or * timeline, if in multi-window mode) and whether each extra ad has been played or not. The * markers are shown in addition to any ad markers for ads in the player's timeline. * * @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or * {@code null} to show no extra ad markers. * @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad * markers. */ public void setExtraAdGroupMarkers( @Nullable long[] extraAdGroupTimesMs, @Nullable boolean[] extraPlayedAdGroups) { Assertions.checkStateNotNull(controller); controller.setExtraAdGroupMarkers(extraAdGroupTimesMs, extraPlayedAdGroups); } /** * Sets the {@link AspectRatioFrameLayout.AspectRatioListener}. * * @param listener The listener to be notified about aspect ratios changes of the video content or * the content frame. */ public void setAspectRatioListener( @Nullable AspectRatioFrameLayout.AspectRatioListener listener) { Assertions.checkStateNotNull(contentFrame); contentFrame.setAspectRatioListener(listener); } /** * Gets the view onto which video is rendered. This is a: * *

    *
  • {@link SurfaceView} by default, or if the {@code surface_type} attribute is set to {@code * surface_view}. *
  • {@link TextureView} if {@code surface_type} is {@code texture_view}. *
  • {@code SphericalGLSurfaceView} if {@code surface_type} is {@code * spherical_gl_surface_view}. *
  • {@code VideoDecoderGLSurfaceView} if {@code surface_type} is {@code * video_decoder_gl_surface_view}. *
  • {@code null} if {@code surface_type} is {@code none}. *
* * @return The {@link SurfaceView}, {@link TextureView}, {@code SphericalGLSurfaceView}, {@code * VideoDecoderGLSurfaceView} or {@code null}. */ @Nullable public View getVideoSurfaceView() { return surfaceView; } /** * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of * the player. * * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and * the overlay is not present. */ @Nullable public FrameLayout getOverlayFrameLayout() { return overlayFrameLayout; } /** * Gets the {@link SubtitleView}. * * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the * subtitle view is not present. */ @Nullable public SubtitleView getSubtitleView() { return subtitleView; } @Override public boolean performClick() { toggleControllerVisibility(); return super.performClick(); } @Override public boolean onTrackballEvent(MotionEvent ev) { if (!useController() || player == null) { return false; } maybeShowController(true); return true; } /** * Should be called when the player is visible to the user, if the {@code surface_type} extends * {@link GLSurfaceView}. It is the counterpart to {@link #onPause()}. * *

This method should typically be called in {@code Activity.onStart()}, or {@code * Activity.onResume()} for API versions <= 23. */ public void onResume() { if (surfaceView instanceof GLSurfaceView) { ((GLSurfaceView) surfaceView).onResume(); } } /** * Should be called when the player is no longer visible to the user, if the {@code surface_type} * extends {@link GLSurfaceView}. It is the counterpart to {@link #onResume()}. * *

This method should typically be called in {@code Activity.onStop()}, or {@code * Activity.onPause()} for API versions <= 23. */ public void onPause() { if (surfaceView instanceof GLSurfaceView) { ((GLSurfaceView) surfaceView).onPause(); } } /** * Called when there's a change in the desired aspect ratio of the content frame. The default * implementation sets the aspect ratio of the content frame to the specified value. * * @param contentFrame The content frame, or {@code null}. * @param aspectRatio The aspect ratio to apply. */ protected void onContentAspectRatioChanged( @Nullable AspectRatioFrameLayout contentFrame, float aspectRatio) { if (contentFrame != null) { contentFrame.setAspectRatio(aspectRatio); } } // AdsLoader.AdViewProvider implementation. @Override public ViewGroup getAdViewGroup() { return Assertions.checkStateNotNull( adOverlayFrameLayout, ""exo_ad_overlay must be present for ad playback""); } @Override public List getAdOverlayInfos() { List overlayViews = new ArrayList<>(); if (overlayFrameLayout != null) { overlayViews.add( new AdOverlayInfo( overlayFrameLayout, AdOverlayInfo.PURPOSE_NOT_VISIBLE, /* detailedReason= */ ""Transparent overlay does not impact viewability"")); } if (controller != null) { overlayViews.add(new AdOverlayInfo(controller, AdOverlayInfo.PURPOSE_CONTROLS)); } return ImmutableList.copyOf(overlayViews); } // Internal methods. @EnsuresNonNullIf(expression = ""controller"", result = true) private boolean useController() { if (useController) { Assertions.checkStateNotNull(controller); return true; } return false; } @EnsuresNonNullIf(expression = ""artworkView"", result = true) private boolean useArtwork() { if (useArtwork) { Assertions.checkStateNotNull(artworkView); return true; } return false; } private void toggleControllerVisibility() { if (!useController() || player == null) { return; } if (!controller.isVisible()) { maybeShowController(true); } else if (controllerHideOnTouch) { controller.hide(); } } /** Shows the playback controls, but only if forced or shown indefinitely. */ private void maybeShowController(boolean isForced) { if (isPlayingAd() && controllerHideDuringAds) { return; } if (useController()) { boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0; boolean shouldShowIndefinitely = shouldShowControllerIndefinitely(); if (isForced || wasShowingIndefinitely || shouldShowIndefinitely) { showController(shouldShowIndefinitely); } } } private boolean shouldShowControllerIndefinitely() { if (player == null) { return true; } int playbackState = player.getPlaybackState(); return controllerAutoShow && (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED || !player.getPlayWhenReady()); } private void showController(boolean showIndefinitely) { if (!useController()) { return; } controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs); controller.show(); } private boolean isPlayingAd() { return player != null && player.isPlayingAd() && player.getPlayWhenReady(); } private void updateForCurrentTrackSelections(boolean isNewPlayer) { @Nullable Player player = this.player; if (player == null || !player.isCommandAvailable(Player.COMMAND_GET_TRACKS) || player.getCurrentTracks().isEmpty()) { if (!keepContentOnPlayerReset) { hideArtwork(); closeShutter(); } return; } if (isNewPlayer && !keepContentOnPlayerReset) { // Hide any video from the previous player. closeShutter(); } if (player.getCurrentTracks().isTypeSelected(C.TRACK_TYPE_VIDEO)) { // Video enabled, so artwork must be hidden. If the shutter is closed, it will be opened // in onRenderedFirstFrame(). hideArtwork(); return; } // Video disabled so the shutter must be closed. closeShutter(); // Display artwork if enabled and available, else hide it. if (useArtwork()) { if (setArtworkFromMediaMetadata(player.getMediaMetadata())) { return; } if (setDrawableArtwork(defaultArtwork)) { return; } } // Artwork disabled or unavailable. hideArtwork(); } private void updateAspectRatio() { VideoSize videoSize = player != null ? player.getVideoSize() : VideoSize.UNKNOWN; int width = videoSize.width; int height = videoSize.height; int unappliedRotationDegrees = videoSize.unappliedRotationDegrees; float videoAspectRatio = (height == 0 || width == 0) ? 0 : (width * videoSize.pixelWidthHeightRatio) / height; if (surfaceView instanceof TextureView) { // Try to apply rotation transformation when our surface is a TextureView. if (videoAspectRatio > 0 && (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270)) { // We will apply a rotation 90/270 degree to the output texture of the TextureView. // In this case, the output video's width and height will be swapped. videoAspectRatio = 1 / videoAspectRatio; } if (textureViewRotation != 0) { surfaceView.removeOnLayoutChangeListener(componentListener); } textureViewRotation = unappliedRotationDegrees; if (textureViewRotation != 0) { // The texture view's dimensions might be changed after layout step. // So add an OnLayoutChangeListener to apply rotation after layout step. surfaceView.addOnLayoutChangeListener(componentListener); } applyTextureViewRotation((TextureView) surfaceView, textureViewRotation); } onContentAspectRatioChanged( contentFrame, surfaceViewIgnoresVideoAspectRatio ? 0 : videoAspectRatio); } @RequiresNonNull(""artworkView"") private boolean setArtworkFromMediaMetadata(MediaMetadata mediaMetadata) { if (mediaMetadata.artworkData == null) { return false; } Bitmap bitmap = BitmapFactory.decodeByteArray( mediaMetadata.artworkData, /* offset= */ 0, mediaMetadata.artworkData.length); return setDrawableArtwork(new BitmapDrawable(getResources(), bitmap)); } @RequiresNonNull(""artworkView"") private boolean setDrawableArtwork(@Nullable Drawable drawable) { if (drawable != null) { int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); if (drawableWidth > 0 && drawableHeight > 0) { float artworkAspectRatio = (float) drawableWidth / drawableHeight; onContentAspectRatioChanged(contentFrame, artworkAspectRatio); artworkView.setImageDrawable(drawable); artworkView.setVisibility(VISIBLE); return true; } } return false; } private void hideArtwork() { if (artworkView != null) { artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference. artworkView.setVisibility(INVISIBLE); } } private void closeShutter() { if (shutterView != null) { shutterView.setVisibility(View.VISIBLE); } } private void updateBuffering() { if (bufferingView != null) { boolean showBufferingSpinner = player != null && player.getPlaybackState() == Player.STATE_BUFFERING && (showBuffering == SHOW_BUFFERING_ALWAYS || (showBuffering == SHOW_BUFFERING_WHEN_PLAYING && player.getPlayWhenReady())); bufferingView.setVisibility(showBufferingSpinner ? View.VISIBLE : View.GONE); } } private void updateErrorMessage() { if (errorMessageView != null) { if (customErrorMessage != null) { errorMessageView.setText(customErrorMessage); errorMessageView.setVisibility(View.VISIBLE); return; } @Nullable PlaybackException error = player != null ? player.getPlayerError() : null; if (error != null && errorMessageProvider != null) { CharSequence errorMessage = errorMessageProvider.getErrorMessage(error).second; errorMessageView.setText(errorMessage); errorMessageView.setVisibility(View.VISIBLE); } else { errorMessageView.setVisibility(View.GONE); } } } private void updateContentDescription() { if (controller == null || !useController) { setContentDescription(/* contentDescription= */ null); } else if (controller.getVisibility() == View.VISIBLE) { setContentDescription( /* contentDescription= */ controllerHideOnTouch ? getResources().getString(R.string.exo_controls_hide) : null); } else { setContentDescription( /* contentDescription= */ getResources().getString(R.string.exo_controls_show)); } } private void updateControllerVisibility() { if (isPlayingAd() && controllerHideDuringAds) { hideController(); } else { maybeShowController(false); } } @RequiresApi(23) private static void configureEditModeLogoV23( Context context, Resources resources, ImageView logo) { logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null)); } private static void configureEditModeLogo(Context context, Resources resources, ImageView logo) { logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color)); } @SuppressWarnings(""ResourceType"") private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) { aspectRatioFrame.setResizeMode(resizeMode); } /** Applies a texture rotation to a {@link TextureView}. */ private static void applyTextureViewRotation(TextureView textureView, int textureViewRotation) { Matrix transformMatrix = new Matrix(); float textureViewWidth = textureView.getWidth(); float textureViewHeight = textureView.getHeight(); if (textureViewWidth != 0 && textureViewHeight != 0 && textureViewRotation != 0) { float pivotX = textureViewWidth / 2; float pivotY = textureViewHeight / 2; transformMatrix.postRotate(textureViewRotation, pivotX, pivotY); // After rotation, scale the rotated texture to fit the TextureView size. RectF originalTextureRect = new RectF(0, 0, textureViewWidth, textureViewHeight); RectF rotatedTextureRect = new RectF(); transformMatrix.mapRect(rotatedTextureRect, originalTextureRect); transformMatrix.postScale( textureViewWidth / rotatedTextureRect.width(), textureViewHeight / rotatedTextureRect.height(), pivotX, pivotY); } textureView.setTransform(transformMatrix); } @SuppressLint(""InlinedApi"") private boolean isDpadKey(int keyCode) { return keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_UP_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_CENTER; } private final class ComponentListener implements Player.Listener, OnLayoutChangeListener, OnClickListener, PlayerControlView.VisibilityListener { private final Period period; private @Nullable Object lastPeriodUidWithTracks; public ComponentListener() { period = new Period(); } // Player.Listener implementation @Override public void onCues(CueGroup cueGroup) { if (subtitleView != null) { subtitleView.setCues(cueGroup.cues); } } @Override public void onVideoSizeChanged(VideoSize videoSize) { updateAspectRatio(); } @Override public void onRenderedFirstFrame() { if (shutterView != null) { shutterView.setVisibility(INVISIBLE); } } @Override public void onTracksChanged(Tracks tracks) { // Suppress the update if transitioning to an unprepared period within the same window. This // is necessary to avoid closing the shutter when such a transition occurs. See: // https://github.com/google/ExoPlayer/issues/5507. Player player = Assertions.checkNotNull(PlayerView.this.player); Timeline timeline = player.getCurrentTimeline(); if (timeline.isEmpty()) { lastPeriodUidWithTracks = null; } else if (!player.getCurrentTracks().isEmpty()) { lastPeriodUidWithTracks = timeline.getPeriod(player.getCurrentPeriodIndex(), period, /* setIds= */ true).uid; } else if (lastPeriodUidWithTracks != null) { int lastPeriodIndexWithTracks = timeline.getIndexOfPeriod(lastPeriodUidWithTracks); if (lastPeriodIndexWithTracks != C.INDEX_UNSET) { int [MASK] = timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex; if (player.getCurrentMediaItemIndex() == [MASK] ) { // We're in the same media item. Suppress the update. return; } } lastPeriodUidWithTracks = null; } updateForCurrentTrackSelections(/* isNewPlayer= */ false); } @Override public void onPlaybackStateChanged(@Player.State int playbackState) { updateBuffering(); updateErrorMessage(); updateControllerVisibility(); } @Override public void onPlayWhenReadyChanged( boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) { updateBuffering(); updateControllerVisibility(); } @Override public void onPositionDiscontinuity( Player.PositionInfo oldPosition, Player.PositionInfo newPosition, @DiscontinuityReason int reason) { if (isPlayingAd() && controllerHideDuringAds) { hideController(); } } // OnLayoutChangeListener implementation @Override public void onLayoutChange( View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { applyTextureViewRotation((TextureView) view, textureViewRotation); } // OnClickListener implementation @Override public void onClick(View view) { toggleControllerVisibility(); } // PlayerControlView.VisibilityListener implementation @Override public void onVisibilityChange(int visibility) { updateContentDescription(); } } } ","lastWindowIndexWithTracks " "package com.github.scribejava.apis.examples; import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.apis.GoogleApi20; import com.github.scribejava.core.model.DeviceAuthorization; import com.github.scribejava.core.model.OAuth2AccessToken; import com.github.scribejava.core.model.OAuthRequest; import com.github.scribejava.core.model.Response; import com.github.scribejava.core.model.Verb; import com.github.scribejava.core.oauth.OAuth20Service; import java.io.IOException; import java.util.Scanner; import java.util.concurrent.ExecutionException; public class Google20DeviceAuthorizationGrantExample { private static final String NETWORK_NAME = ""Google""; private static final String PROTECTED_RESOURCE_URL = ""https://www.googleapis.com/oauth2/v3/userinfo""; private Google20DeviceAuthorizationGrantExample() { } @SuppressWarnings(""PMD.SystemPrintln"") public static void main(String... args) throws IOException, InterruptedException, ExecutionException { // Replace these with your client id and secret final String clientId = ""your client id""; final String clientSecret = ""your_client_secret""; final OAuth20Service service = new ServiceBuilder(clientId) .debug() .apiSecret(clientSecret) .defaultScope(""profile"") // replace with desired scope .build(GoogleApi20.instance()); final Scanner in = new Scanner(System.in, ""UTF-8""); System.out.println(""=== "" + NETWORK_NAME + ""'s OAuth Workflow ===""); System.out.println(); System.out.println(""Requesting a set of verification codes...""); final DeviceAuthorization deviceAuthorization = service.getDeviceAuthorizationCodes(); System.out.println(""Got the Device Authorization Codes!""); System.out.println(deviceAuthorization); System.out.println(""Now go and authorize ScribeJava. Visit: "" + deviceAuthorization.getVerificationUri() + "" and enter the code: "" + deviceAuthorization.getUserCode()); if (deviceAuthorization.getVerificationUriComplete() != null) { System.out.println(""Or visit "" + deviceAuthorization.getVerificationUriComplete()); } System.out.println(""Polling for an Access Token...""); final OAuth2AccessToken [MASK] = service.pollAccessTokenDeviceAuthorizationGrant(deviceAuthorization); System.out.println(""Got the Access Token!""); System.out.println(""(The raw response looks like this: "" + [MASK] .getRawResponse() + ""')""); // Now let's go and ask for a protected resource! System.out.println(""Now we're going to access a protected resource...""); while (true) { System.out.println(""Paste fieldnames to fetch (leave empty to get profile, 'exit' to stop the example)""); System.out.print("">>""); final String query = in.nextLine(); System.out.println(); final String requestUrl; if (""exit"".equals(query)) { break; } else if (query == null || query.isEmpty()) { requestUrl = PROTECTED_RESOURCE_URL; } else { requestUrl = PROTECTED_RESOURCE_URL + ""?fields="" + query; } final OAuthRequest request = new OAuthRequest(Verb.GET, requestUrl); service.signRequest( [MASK] , request); System.out.println(); try (Response response = service.execute(request)) { System.out.println(response.getCode()); System.out.println(response.getBody()); } System.out.println(); } } } ","accessToken " "// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.j2objc.testing; import com.google.j2objc.annotations.AutoreleasePool; import com.google.j2objc.annotations.WeakOuter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.Set; import junit.framework.Test; import junit.runner.Version; import org.junit.internal.TextListener; import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.RunWith; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import org.junit.runners.Suite; /*-[ #include ]-*/ /** * Runs JUnit test classes. * * Provides a main() function that runs all JUnit tests linked into the executable. * The main() function accepts no arguments since Pulse unit tests are not designed to accept * arguments. Instead the code expects a file called ""JUnitTestRunner.properties"" to be include * as a resource. * * Any classes derived from {@link Test} (JUnit 3) or {@link Suite} (JUnit 4) are considered * JUnit tests. This behavior can be changed by overriding {@link #isJUnitTestClass}, * {@link #isJUnit3TestClass} or {@link #isJUnit4TestClass}. * * @author iroth@google.com (Ian Roth) */ public class JUnitTestRunner { private static final String PROPERTIES_FILE_NAME = ""JUnitTestRunner.properties""; /** * Specifies the output format for tests. */ public enum OutputFormat { JUNIT, // JUnit style output. GTM_UNIT_TESTING // Google Toolkit for Mac unit test output format. } /** * Specifies the sort order for tests. */ public enum SortOrder { ALPHABETICAL, // Sorted alphabetically RANDOM // Sorted randomly (differs with each run) } /** * Specifies whether a pattern includes or excludes test classes. */ public enum TestInclusion { RUN_TEST, // Includes tests that exactly match the pattern INCLUDE, // Includes test classes matching the pattern EXCLUDE // Excludes test classes matching the pattern } private final PrintStream out; private final Set testsToRun = new HashSet<>(); private final Set includePatterns = new HashSet<>(); private final Set excludePatterns = new HashSet<>(); private final Map nameMappings = new HashMap<>(); private final Map randomNames = new HashMap<>(); private final Random random = new Random(System.currentTimeMillis()); private OutputFormat outputFormat = OutputFormat.JUNIT; private SortOrder sortOrder = SortOrder.ALPHABETICAL; public JUnitTestRunner() { this(System.err); } public JUnitTestRunner(PrintStream out) { this.out = out; } public static int main(String[] args) { // Create JUnit test runner. PrintStream nsLogOut = new PrintStream(new NSLogOutputStream(), true); JUnitTestRunner runner = new JUnitTestRunner(nsLogOut); runner.loadPropertiesFromResource(PROPERTIES_FILE_NAME); return runner.run(); } /** * Runs the test classes given in {@param classes}. * @returns Zero if all tests pass, non-zero otherwise. */ public static int run(Class[] classes, RunListener listener) { JUnitCore junitCore = new JUnitCore(); junitCore.addListener(listener); boolean hasError = false; for (@AutoreleasePool Class c : classes) { Result result = junitCore.run(c); hasError = hasError || !result.wasSuccessful(); } return hasError ? 1 : 0; } /** * Runs the test classes that match settings in {@link #PROPERTIES_FILE_NAME}. * @returns Zero if all tests pass, non-zero otherwise. */ public int run() { if (outputFormat == OutputFormat.GTM_UNIT_TESTING) { Thread.setDefaultUncaughtExceptionHandler(new GtmUncaughtExceptionHandler()); } Set> classesSet = getTestClasses(); Class[] classes = classesSet.toArray(new Class[classesSet.size()]); sortClasses(classes, sortOrder); RunListener listener = newRunListener(outputFormat); return run(classes, listener); } /** * Returns a new {@link RunListener} instance for the given {@param outputFormat}. */ public RunListener newRunListener(OutputFormat outputFormat) { switch (outputFormat) { case JUNIT: out.println(""JUnit version "" + Version.id()); return new TextListener(out); case GTM_UNIT_TESTING: return new GtmUnitTestingTextListener(); default: throw new IllegalArgumentException(""outputFormat""); } } /** * Sorts the classes given in {@param classes} according to {@param sortOrder}. */ public void sortClasses(Class[] classes, final SortOrder sortOrder) { Arrays.sort(classes, new Comparator>() { @Override public int compare(Class class1, Class class2) { String name1 = getSortKey(class1, sortOrder); String name2 = getSortKey(class2, sortOrder); return name1.compareTo(name2); } }); } private String replaceAll(String value) { for (Map.Entry entry : nameMappings.entrySet()) { String pattern = entry.getKey(); String replacement = entry.getValue(); value = value.replaceAll(pattern, replacement); } return value; } private String getSortKey(Class cls, SortOrder sortOrder) { String className = cls.getName(); switch (sortOrder) { case ALPHABETICAL: return replaceAll(className); case RANDOM: String sortKey = randomNames.get(className); if (sortKey == null) { sortKey = Integer.toString(random.nextInt()); randomNames.put(className, sortKey); } return sortKey; default: throw new IllegalArgumentException(""sortOrder""); } } /*-[ // Returns true if |cls| conforms to the NSObject protocol. BOOL IsNSObjectClass(Class cls) { while (cls != nil) { if (class_conformsToProtocol(cls, @protocol(NSObject))) { return YES; } // class_conformsToProtocol() does not examine superclasses. cls = class_getSuperclass(cls); } return NO; } ]-*/ /** * Returns the set of all loaded JUnit test classes. */ private native Set> getAllTestClasses() /*-[ int classCount = objc_getClassList(NULL, 0); Class *classes = (Class *)malloc(classCount * sizeof(Class)); objc_getClassList(classes, classCount); id result = AUTORELEASE([[JavaUtilHashSet alloc] init]); for (int i = 0; i < classCount; i++) { @try { Class cls = classes[i]; if (IsNSObjectClass(cls)) { IOSClass *javaClass = IOSClass_fromClass(cls); if ([self isJUnitTestClassWithIOSClass:javaClass]) { [result addWithId:javaClass]; } } } @catch (NSException *e) { // Ignore any exceptions thrown by class initialization. } } free(classes); return result; ]-*/; /** * @return true if {@param cls} is either a JUnit 3 or JUnit 4 test. */ protected boolean isJUnitTestClass(Class cls) { return !Modifier.isAbstract(cls.getModifiers()) && (isJUnit3TestClass(cls) || isJUnit4TestClass(cls)); } /** * @return true if {@param cls} derives from {@link Test} and is not part of the * {@link junit.framework} package. */ protected boolean isJUnit3TestClass(Class cls) { if (Test.class.isAssignableFrom(cls)) { String packageName = getPackageName(cls); return !packageName.startsWith(""junit.framework"") && !packageName.startsWith(""junit.extensions""); } return false; } /** @return true if {@param cls} is {@link RunWith} annotated. */ protected boolean isJUnit4TestClass(Class cls) { return cls.getAnnotation(RunWith.class) != null; } /** * Returns the name of a class's package or """" for the default package * or (for Foundation classes) no package object. */ private String getPackageName(Class cls) { Package pkg = cls.getPackage(); return pkg != null ? pkg.getName() : """"; } /** * Returns the set of test classes that match settings in {@link #PROPERTIES_FILE_NAME}. */ private Set> getTestClasses() { Set> testClasses = new HashSet<>(); for (String testName : testsToRun) { try { testClasses.add(Class.forName(testName)); } catch (ClassNotFoundException e) { throw new AssertionError(e); } } if (!includePatterns.isEmpty()) { for (Class testClass : getAllTestClasses()) { for (String includePattern : includePatterns) { if (matchesPattern(testClass, includePattern)) { testClasses.add(testClass); break; } } } } if (testsToRun.isEmpty() && includePatterns.isEmpty()) { // Include all tests if no include patterns specified. testClasses.addAll(getAllTestClasses()); } // Search included tests for tests to exclude. Iterator> testClassesIterator = testClasses.iterator(); while (testClassesIterator.hasNext()) { Class testClass = testClassesIterator.next(); for (String excludePattern : excludePatterns) { if (matchesPattern(testClass, excludePattern)) { testClassesIterator.remove(); break; } } } return testClasses; } private boolean matchesPattern(Class testClass, String pattern) { return testClass.getCanonicalName().contains(pattern); } private void loadProperties(InputStream stream) { Properties properties = new Properties(); try { properties.load(stream); } catch (IOException e) { onError(e); } Set propertyNames = properties.stringPropertyNames(); for (String key : propertyNames) { String value = properties.getProperty(key); try { if (key.equals(""outputFormat"")) { outputFormat = OutputFormat.valueOf(value); } else if (key.equals(""sortOrder"")) { sortOrder = SortOrder.valueOf(value); } else if (value.equals(TestInclusion.RUN_TEST.name())) { testsToRun.add(key); } else if (value.equals(TestInclusion.INCLUDE.name())) { includePatterns.add(key); } else if (value.equals(TestInclusion.EXCLUDE.name())) { excludePatterns.add(key); } else { nameMappings.put(key, value); } } catch (IllegalArgumentException e) { onError(e); } } } private void loadPropertiesFromResource(String resourcePath) { try { InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath); if (stream != null) { loadProperties(stream); } else { throw new IOException(String.format(""Resource not found: %s"", resourcePath)); } } catch (Exception e) { onError(e); } } private void onError(Exception e) { e.printStackTrace(out); } private class GtmUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { out.print(""Exception in thread \"""" + t.getName() + ""\"" ""); e.printStackTrace(out); out.println(""** TEST FAILED **""); } } @WeakOuter private class GtmUnitTestingTextListener extends RunListener { private int numTests = 0; private int numFailures = 0; private final int numUnexpected = 0; // Never changes, but required in output. private Failure testFailure; private double testStartTime; @Override public void testRunFinished(Result result) throws Exception { printf(""Executed %d tests, with %d failures (%d unexpected)\n"", numTests, numFailures, numUnexpected); } @Override public void testStarted(Description description) throws Exception { numTests++; testFailure = null; testStartTime = System.currentTimeMillis(); printf(""Test Case '-[%s]' started.\n"", parseDescription(description)); } @Override public void testFinished(Description description) throws Exception { double testEndTime = System.currentTimeMillis(); double [MASK] = 0.001 * (testEndTime - testStartTime); String statusMessage = ""passed""; if (testFailure != null) { statusMessage = ""failed""; out.print(testFailure.getTrace()); } printf(""Test Case '-[%s]' %s (%.3f seconds).\n\n"", parseDescription(description), statusMessage, [MASK] ); } @Override public void testFailure(Failure failure) throws Exception { testFailure = failure; numFailures++; } private String parseDescription(Description description) { String displayName = description.getDisplayName(); int p1 = displayName.indexOf(""(""); int p2 = displayName.indexOf("")""); if (p1 < 0 || p2 < 0 || p2 <= p1) { return displayName; } String methodName = displayName.substring(0, p1); String className = displayName.substring(p1 + 1, p2); return replaceAll(className) + "" "" + methodName; } private void printf(String format, Object... args) { // Avoid using printf() or println() because they will be flushed in pieces and cause // interleaving with logger messages. out.print(String.format(format, args)); } } /** * Logs test runner output using NSLog(). */ private static class NSLogOutputStream extends OutputStream { private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); @Override public void write(int b) throws IOException { buffer.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { buffer.write(b, off, len); } @Override public native void flush() /*-[ NSString *s = [buffer_ toStringWithNSString:@""UTF-8""]; if (s.length) { NSLog(@""%@"", s); } [buffer_ reset]; ]-*/; } } ","elapsedSeconds " "package org.junit.rules; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import java.io.File; import java.io.IOException; import java.io.InterruptedIOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class TimeoutRuleTest { private static final ReentrantLock run1Lock = new ReentrantLock(); private static volatile boolean run4done = false; public abstract static class AbstractTimeoutTest { public static final StringBuffer logger = new StringBuffer(); @Rule public final TemporaryFolder tmpFile = new TemporaryFolder(); @Test public void run1() throws InterruptedException { logger.append(""run1""); TimeoutRuleTest.run1Lock.lockInterruptibly(); TimeoutRuleTest.run1Lock.unlock(); } @Test public void run2() throws InterruptedException { logger.append(""run2""); Thread.currentThread().join(); } @Test public synchronized void run3() throws InterruptedException { logger.append(""run3""); wait(); } @Test public void run4() { logger.append(""run4""); while (!run4done) { } } @Test public void run5() throws IOException { logger.append(""run5""); Random rnd = new Random(); byte[] data = new byte[1024]; File tmp = tmpFile.newFile(); while (true) { RandomAccessFile randomAccessFile = new RandomAccessFile(tmp, ""rw""); try { FileChannel channel = randomAccessFile.getChannel(); rnd.nextBytes(data); ByteBuffer buffer = ByteBuffer.wrap(data); // Interrupted thread closes channel and throws ClosedByInterruptException. channel.write(buffer); } finally { randomAccessFile.close(); } tmp.delete(); } } @Test public void run6() throws InterruptedIOException { logger.append(""run6""); // Java IO throws InterruptedIOException only on SUN machines. throw new InterruptedIOException(); } } public static class HasGlobalLongTimeout extends AbstractTimeoutTest { @Rule public final TestRule globalTimeout = Timeout.millis(200); } public static class HasGlobalTimeUnitTimeout extends AbstractTimeoutTest { @Rule public final TestRule globalTimeout = new Timeout(200, TimeUnit.MILLISECONDS); } public static class HasNullTimeUnit { @Rule public final TestRule globalTimeout = new Timeout(200, null); @Test public void wouldPass() { } } @Before public void before() { run4done = false; run1Lock.lock(); } @After public void after() { // set run4done to make sure that the thread won't continue at run4() run4done = true; run1Lock.unlock(); } @Test public void timeUnitTimeout() { HasGlobalTimeUnitTimeout.logger.setLength(0); Result result = JUnitCore.runClasses(HasGlobalTimeUnitTimeout.class); assertEquals(6, result.getFailureCount()); assertThat(HasGlobalTimeUnitTimeout.logger.toString(), containsString(""run1"")); assertThat(HasGlobalTimeUnitTimeout.logger.toString(), containsString(""run2"")); assertThat(HasGlobalTimeUnitTimeout.logger.toString(), containsString(""run3"")); assertThat(HasGlobalTimeUnitTimeout.logger.toString(), containsString(""run4"")); assertThat(HasGlobalTimeUnitTimeout.logger.toString(), containsString(""run5"")); assertThat(HasGlobalTimeUnitTimeout.logger.toString(), containsString(""run6"")); } @Test public void longTimeout() { HasGlobalLongTimeout.logger.setLength(0); Result result = JUnitCore.runClasses(HasGlobalLongTimeout.class); assertEquals(6, result.getFailureCount()); assertThat(HasGlobalLongTimeout.logger.toString(), containsString(""run1"")); assertThat(HasGlobalLongTimeout.logger.toString(), containsString(""run2"")); assertThat(HasGlobalLongTimeout.logger.toString(), containsString(""run3"")); assertThat(HasGlobalLongTimeout.logger.toString(), containsString(""run4"")); assertThat(HasGlobalLongTimeout.logger.toString(), containsString(""run5"")); assertThat(HasGlobalLongTimeout.logger.toString(), containsString(""run6"")); } @Test public void nullTimeUnit() { Result result = JUnitCore.runClasses(HasNullTimeUnit.class); assertEquals(1, result.getFailureCount()); Failure failure = result.getFailures().get(0); assertThat(failure.getException().getMessage(), containsString(""Invalid parameters for Timeout"")); Throwable [MASK] = failure.getException().getCause(); assertThat( [MASK] .getMessage(), containsString(""TimeUnit cannot be null"")); } } ","cause " "package com.alibaba.json.bvt.issue_3400; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.annotation.JSONCreator; import com.alibaba.fastjson.annotation.JSONType; import junit.framework.TestCase; import org.springframework.core.io.FileSystemResource; public class Issue3436 extends TestCase { public void test_for_issue() throws Exception { JSON.addMixInAnnotations(FileSystemResource.class, FileSystemResourceMixedIn.class); FileSystemResource [MASK] = new FileSystemResource(""E:\\my-code\\test\\test-fastjson.txt""); String json = JSON.toJSONString( [MASK] ); assertEquals(""{\""path\"":\""E:/my-code/test/test-fastjson.txt\""}"", json); FileSystemResource fsr1 = JSON.parseObject(json, FileSystemResource.class); assertEquals( [MASK] .getPath(), fsr1.getPath()); System.out.println(""file size after Serialize:"" + [MASK] .getFile().length()); } @JSONType(asm = false, includes = ""path"") public static class FileSystemResourceMixedIn { @JSONCreator public FileSystemResourceMixedIn(String path) { } } } ","fileSystemResource " "/* * Copyright 2013 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.ssl; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultChannelId; import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalChannel; import io.netty.channel.local.LocalServerChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.CodecException; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.UnsupportedMessageTypeException; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.handler.ssl.util.SelfSignedCertificate; import io.netty.util.AbstractReferenceCounted; import io.netty.util.IllegalReferenceCountException; import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCounted; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.ImmediateExecutor; import io.netty.util.concurrent.Promise; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.PlatformDependent; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.Executable; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.ClosedChannelException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; import java.util.Queue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLProtocolException; import javax.net.ssl.X509ExtendedTrustManager; import static io.netty.buffer.Unpooled.wrappedBuffer; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeTrue; public class SslHandlerTest { private static final Executor DIRECT_EXECUTOR = new Executor() { @Override public void execute(Runnable command) { command.run(); } }; @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testNonApplicationDataFailureFailsQueuedWrites() throws NoSuchAlgorithmException, InterruptedException { final CountDownLatch writeLatch = new CountDownLatch(1); final Queue writesToFail = new ConcurrentLinkedQueue(); SSLEngine engine = newClientModeSSLEngine(); SslHandler handler = new SslHandler(engine) { @Override public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { super.write(ctx, msg, promise); writeLatch.countDown(); } }; EmbeddedChannel ch = new EmbeddedChannel(new ChannelDuplexHandler() { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { if (msg instanceof ByteBuf) { if (((ByteBuf) msg).isReadable()) { writesToFail.add(promise); } else { promise.setSuccess(); } } ReferenceCountUtil.release(msg); } }, handler); try { final CountDownLatch writeCauseLatch = new CountDownLatch(1); final AtomicReference failureRef = new AtomicReference(); ch.write(Unpooled.wrappedBuffer(new byte[]{1})).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { failureRef.compareAndSet(null, future.cause()); writeCauseLatch.countDown(); } }); writeLatch.await(); // Simulate failing the SslHandler non-application writes after there are applications writes queued. ChannelPromise promiseToFail; while ((promiseToFail = writesToFail.poll()) != null) { promiseToFail.setFailure(new RuntimeException(""fake exception"")); } writeCauseLatch.await(); Throwable writeCause = failureRef.get(); assertNotNull(writeCause); assertThat(writeCause, is(CoreMatchers.instanceOf(SSLException.class))); Throwable cause = handler.handshakeFuture().cause(); assertNotNull(cause); assertThat(cause, is(CoreMatchers.instanceOf(SSLException.class))); } finally { assertFalse(ch.finishAndReleaseAll()); } } @Test public void testNoSslHandshakeEventWhenNoHandshake() throws Exception { final AtomicBoolean inActive = new AtomicBoolean(false); SSLEngine engine = SSLContext.getDefault().createSSLEngine(); EmbeddedChannel ch = new EmbeddedChannel( DefaultChannelId.newInstance(), false, false, new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // Not forward the event to the SslHandler but just close the Channel. ctx.close(); } }, new SslHandler(engine) { @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // We want to override what Channel.isActive() will return as otherwise it will // return true and so trigger an handshake. inActive.set(true); super.handlerAdded(ctx); inActive.set(false); } }, new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof SslHandshakeCompletionEvent) { throw (Exception) ((SslHandshakeCompletionEvent) evt).cause(); } } }) { @Override public boolean isActive() { return !inActive.get() && super.isActive(); } }; ch.register(); assertFalse(ch.finishAndReleaseAll()); } @Test @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void testClientHandshakeTimeout() throws Exception { assertThrows(SslHandshakeTimeoutException.class, new Executable() { @Override public void execute() throws Throwable { testHandshakeTimeout(true); } }); } @Test @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) public void testServerHandshakeTimeout() throws Exception { assertThrows(SslHandshakeTimeoutException.class, new Executable() { @Override public void execute() throws Throwable { testHandshakeTimeout(false); } }); } private static SSLEngine newServerModeSSLEngine() throws NoSuchAlgorithmException { SSLEngine engine = SSLContext.getDefault().createSSLEngine(); // Set the mode before we try to do the handshake as otherwise it may throw an IllegalStateException. // See: // - https://docs.oracle.com/javase/10/docs/api/javax/net/ssl/SSLEngine.html#beginHandshake() // - https://mail.openjdk.java.net/pipermail/security-dev/2018-July/017715.html engine.setUseClientMode(false); return engine; } private static SSLEngine newClientModeSSLEngine() throws NoSuchAlgorithmException { SSLEngine engine = SSLContext.getDefault().createSSLEngine(); // Set the mode before we try to do the handshake as otherwise it may throw an IllegalStateException. // See: // - https://docs.oracle.com/javase/10/docs/api/javax/net/ssl/SSLEngine.html#beginHandshake() // - https://mail.openjdk.java.net/pipermail/security-dev/2018-July/017715.html engine.setUseClientMode(true); return engine; } private static void testHandshakeTimeout(boolean client) throws Exception { SSLEngine engine = SSLContext.getDefault().createSSLEngine(); engine.setUseClientMode(client); SslHandler handler = new SslHandler(engine); handler.setHandshakeTimeoutMillis(1); EmbeddedChannel ch = new EmbeddedChannel(handler); try { while (!handler.handshakeFuture().isDone()) { Thread.sleep(10); // We need to run all pending tasks as the handshake timeout is scheduled on the EventLoop. ch.runPendingTasks(); } handler.handshakeFuture().syncUninterruptibly(); } finally { ch.finishAndReleaseAll(); } } @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testHandshakeAndClosePromiseFailedOnRemoval() throws Exception { SSLEngine engine = SSLContext.getDefault().createSSLEngine(); engine.setUseClientMode(true); SslHandler handler = new SslHandler(engine); final AtomicReference handshakeRef = new AtomicReference(); final AtomicReference closeRef = new AtomicReference(); EmbeddedChannel ch = new EmbeddedChannel(handler, new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof SslHandshakeCompletionEvent) { handshakeRef.set(((SslHandshakeCompletionEvent) evt).cause()); } else if (evt instanceof SslCloseCompletionEvent) { closeRef.set(((SslCloseCompletionEvent) evt).cause()); } } }); assertFalse(handler.handshakeFuture().isDone()); assertFalse(handler.sslCloseFuture().isDone()); ch.pipeline().remove(handler); try { while (!handler.handshakeFuture().isDone() || handshakeRef.get() == null || !handler.sslCloseFuture().isDone() || closeRef.get() == null) { Thread.sleep(10); // Continue running all pending tasks until we notified for everything. ch.runPendingTasks(); } assertSame(handler.handshakeFuture().cause(), handshakeRef.get()); assertSame(handler.sslCloseFuture().cause(), closeRef.get()); } finally { ch.finishAndReleaseAll(); } } @Test public void testTruncatedPacket() throws Exception { SSLEngine engine = newServerModeSSLEngine(); final EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(engine)); // Push the first part of a 5-byte handshake message. ch.writeInbound(wrappedBuffer(new byte[]{22, 3, 1, 0, 5})); // Should decode nothing yet. assertThat(ch.readInbound(), is(nullValue())); DecoderException e = assertThrows(DecoderException.class, new Executable() { @Override public void execute() throws Throwable { // Push the second part of the 5-byte handshake message. ch.writeInbound(wrappedBuffer(new byte[]{2, 0, 0, 1, 0})); } }); // Be sure we cleanup the channel and release any pending messages that may have been generated because // of an alert. // See https://github.com/netty/netty/issues/6057. ch.finishAndReleaseAll(); // The pushed message is invalid, so it should raise an exception if it decoded the message correctly. assertThat(e.getCause(), is(instanceOf(SSLProtocolException.class))); } @Test public void testNonByteBufWriteIsReleased() throws Exception { SSLEngine engine = newServerModeSSLEngine(); final EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(engine)); final AbstractReferenceCounted referenceCounted = new AbstractReferenceCounted() { @Override public ReferenceCounted touch(Object hint) { return this; } @Override protected void deallocate() { } }; ExecutionException e = assertThrows(ExecutionException.class, new Executable() { @Override public void execute() throws Throwable { ch.write(referenceCounted).get(); } }); assertThat(e.getCause(), is(instanceOf(UnsupportedMessageTypeException.class))); assertEquals(0, referenceCounted.refCnt()); assertTrue(ch.finishAndReleaseAll()); } @Test public void testNonByteBufNotPassThrough() throws Exception { SSLEngine engine = newServerModeSSLEngine(); final EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(engine)); assertThrows(UnsupportedMessageTypeException.class, new Executable() { @Override public void execute() throws Throwable { ch.writeOutbound(new Object()); } }); ch.finishAndReleaseAll(); } @Test public void testIncompleteWriteDoesNotCompletePromisePrematurely() throws NoSuchAlgorithmException { SSLEngine engine = newServerModeSSLEngine(); EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(engine)); ChannelPromise promise = ch.newPromise(); ByteBuf buf = Unpooled.buffer(10).writeZero(10); ch.writeAndFlush(buf, promise); assertFalse(promise.isDone()); assertTrue(ch.finishAndReleaseAll()); assertTrue(promise.isDone()); assertThat(promise.cause(), is(instanceOf(SSLException.class))); } @Test public void testReleaseSslEngine() throws Exception { OpenSsl.ensureAvailability(); SelfSignedCertificate cert = new SelfSignedCertificate(); try { SslContext sslContext = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()) .sslProvider(SslProvider.OPENSSL) .build(); try { assertEquals(1, ((ReferenceCounted) sslContext).refCnt()); SSLEngine sslEngine = sslContext.newEngine(ByteBufAllocator.DEFAULT); EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(sslEngine)); assertEquals(2, ((ReferenceCounted) sslContext).refCnt()); assertEquals(1, ((ReferenceCounted) sslEngine).refCnt()); assertTrue(ch.finishAndReleaseAll()); ch.close().syncUninterruptibly(); assertEquals(1, ((ReferenceCounted) sslContext).refCnt()); assertEquals(0, ((ReferenceCounted) sslEngine).refCnt()); } finally { ReferenceCountUtil.release(sslContext); } } finally { cert.delete(); } } private static final class TlsReadTest extends ChannelOutboundHandlerAdapter { private volatile boolean readIssued; @Override public void read(ChannelHandlerContext ctx) throws Exception { readIssued = true; super.read(ctx); } public void test(final boolean dropChannelActive) throws Exception { SSLEngine engine = SSLContext.getDefault().createSSLEngine(); engine.setUseClientMode(true); EmbeddedChannel ch = new EmbeddedChannel(false, false, this, new SslHandler(engine), new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { if (!dropChannelActive) { ctx.fireChannelActive(); } } } ); ch.config().setAutoRead(false); assertFalse(ch.config().isAutoRead()); ch.register(); assertTrue(readIssued); readIssued = false; assertTrue(ch.writeOutbound(Unpooled.EMPTY_BUFFER)); assertTrue(readIssued); assertTrue(ch.finishAndReleaseAll()); } } @Test public void testIssueReadAfterActiveWriteFlush() throws Exception { // the handshake is initiated by channelActive new TlsReadTest().test(false); } @Test public void testIssueReadAfterWriteFlushActive() throws Exception { // the handshake is initiated by flush new TlsReadTest().test(true); } @Test @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) public void testRemoval() throws Exception { NioEventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; Channel cc = null; try { final Promise clientPromise = group.next().newPromise(); Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(newHandler(SslContextBuilder.forClient().trustManager( InsecureTrustManagerFactory.INSTANCE).build(), clientPromise)); SelfSignedCertificate ssc = new SelfSignedCertificate(); final Promise serverPromise = group.next().newPromise(); ServerBootstrap serverBootstrap = new ServerBootstrap() .group(group, group) .channel(NioServerSocketChannel.class) .childHandler(newHandler(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(), serverPromise)); sc = serverBootstrap.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); cc = bootstrap.connect(sc.localAddress()).syncUninterruptibly().channel(); serverPromise.syncUninterruptibly(); clientPromise.syncUninterruptibly(); } finally { if (cc != null) { cc.close().syncUninterruptibly(); } if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); } } private static ChannelHandler newHandler(final SslContext sslCtx, final Promise promise) { return new ChannelInitializer() { @Override protected void initChannel(final Channel ch) { final SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); sslHandler.setHandshakeTimeoutMillis(1000); ch.pipeline().addFirst(sslHandler); sslHandler.handshakeFuture().addListener(new FutureListener() { @Override public void operationComplete(final Future future) { ch.pipeline().remove(sslHandler); // Schedule the close so removal has time to propagate exception if any. ch.eventLoop().execute(new Runnable() { @Override public void run() { ch.close(); } }); } }); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { if (cause instanceof CodecException) { cause = cause.getCause(); } if (cause instanceof IllegalReferenceCountException) { promise.setFailure(cause); } } @Override public void channelInactive(ChannelHandlerContext ctx) { promise.trySuccess(null); } }); } }; } @Test public void testCloseFutureNotified() throws Exception { SSLEngine engine = newServerModeSSLEngine(); SslHandler handler = new SslHandler(engine); EmbeddedChannel ch = new EmbeddedChannel(handler); ch.close(); // When the channel is closed the SslHandler will write an empty buffer to the channel. ByteBuf buf = ch.readOutbound(); assertFalse(buf.isReadable()); buf.release(); assertFalse(ch.finishAndReleaseAll()); assertThat(handler.handshakeFuture().cause(), instanceOf(ClosedChannelException.class)); assertThat(handler.sslCloseFuture().cause(), instanceOf(ClosedChannelException.class)); } @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testEventsFired() throws Exception { SSLEngine engine = newServerModeSSLEngine(); final BlockingQueue events = new LinkedBlockingQueue(); EmbeddedChannel channel = new EmbeddedChannel(new SslHandler(engine), new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof SslCompletionEvent) { events.add((SslCompletionEvent) evt); } } }); assertTrue(events.isEmpty()); assertTrue(channel.finishAndReleaseAll()); SslCompletionEvent evt = events.take(); assertTrue(evt instanceof SslHandshakeCompletionEvent); assertThat(evt.cause(), instanceOf(ClosedChannelException.class)); evt = events.take(); assertTrue(evt instanceof SslCloseCompletionEvent); assertThat(evt.cause(), instanceOf(ClosedChannelException.class)); assertTrue(events.isEmpty()); } @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testHandshakeFailBeforeWritePromise() throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); final CountDownLatch latch = new CountDownLatch(2); final CountDownLatch latch2 = new CountDownLatch(2); final BlockingQueue events = new LinkedBlockingQueue(); Channel serverChannel = null; Channel clientChannel = null; EventLoopGroup group = new DefaultEventLoopGroup(); try { ServerBootstrap sb = new ServerBootstrap(); sb.group(group) .channel(LocalServerChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(sslServerCtx.newHandler(ch.alloc())); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) { ByteBuf buf = ctx.alloc().buffer(10); buf.writeZero(buf.capacity()); ctx.writeAndFlush(buf).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { events.add(future); latch.countDown(); } }); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof SslCompletionEvent) { events.add(evt); latch.countDown(); latch2.countDown(); } } }); } }); Bootstrap cb = new Bootstrap(); cb.group(group) .channel(LocalChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addFirst(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) { ByteBuf buf = ctx.alloc().buffer(1000); buf.writeZero(buf.capacity()); ctx.writeAndFlush(buf); } }); } }); serverChannel = sb.bind(new LocalAddress(""SslHandlerTest"")).sync().channel(); clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); latch.await(); SslCompletionEvent evt = (SslCompletionEvent) events.take(); assertTrue(evt instanceof SslHandshakeCompletionEvent); assertThat(evt.cause(), is(instanceOf(SSLException.class))); ChannelFuture future = (ChannelFuture) events.take(); assertThat(future.cause(), is(instanceOf(SSLException.class))); serverChannel.close().sync(); serverChannel = null; clientChannel.close().sync(); clientChannel = null; latch2.await(); evt = (SslCompletionEvent) events.take(); assertTrue(evt instanceof SslCloseCompletionEvent); assertThat(evt.cause(), is(instanceOf(ClosedChannelException.class))); assertTrue(events.isEmpty()); } finally { if (serverChannel != null) { serverChannel.close(); } if (clientChannel != null) { clientChannel.close(); } group.shutdownGracefully(); } } @Test public void writingReadOnlyBufferDoesNotBreakAggregation() throws Exception { SelfSignedCertificate ssc = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); final SslContext sslClientCtx = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; Channel cc = null; final CountDownLatch serverReceiveLatch = new CountDownLatch(1); try { final int expectedBytes = 11; sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(sslServerCtx.newHandler(ch.alloc())); ch.pipeline().addLast(new SimpleChannelInboundHandler() { private int readBytes; @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { readBytes += msg.readableBytes(); if (readBytes >= expectedBytes) { serverReceiveLatch.countDown(); } } }); } }).bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); cc = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(sslClientCtx.newHandler(ch.alloc())); } }).connect(sc.localAddress()).syncUninterruptibly().channel(); // We first write a ReadOnlyBuffer because SslHandler will attempt to take the first buffer and append to it // until there is no room, or the aggregation size threshold is exceeded. We want to verify that we don't // throw when a ReadOnlyBuffer is used and just verify that we don't aggregate in this case. ByteBuf firstBuffer = Unpooled.buffer(10); firstBuffer.writeByte(0); firstBuffer = firstBuffer.asReadOnly(); ByteBuf secondBuffer = Unpooled.buffer(10); secondBuffer.writeZero(secondBuffer.capacity()); cc.write(firstBuffer); cc.writeAndFlush(secondBuffer).syncUninterruptibly(); serverReceiveLatch.countDown(); } finally { if (cc != null) { cc.close().syncUninterruptibly(); } if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); ReferenceCountUtil.release(sslServerCtx); ReferenceCountUtil.release(sslClientCtx); } } @Test @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) public void testCloseOnHandshakeFailure() throws Exception { final SelfSignedCertificate ssc = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.key(), ssc.cert()).build(); final SslContext sslClientCtx = SslContextBuilder.forClient() .trustManager(new SelfSignedCertificate().cert()) .build(); EventLoopGroup group = new NioEventLoopGroup(1); Channel sc = null; Channel cc = null; try { LocalAddress address = new LocalAddress(getClass().getSimpleName() + "".testCloseOnHandshakeFailure""); ServerBootstrap sb = new ServerBootstrap() .group(group) .channel(LocalServerChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(sslServerCtx.newHandler(ch.alloc())); } }); sc = sb.bind(address).syncUninterruptibly().channel(); final AtomicReference sslHandlerRef = new AtomicReference(); Bootstrap b = new Bootstrap() .group(group) .channel(LocalChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { SslHandler handler = sslClientCtx.newHandler(ch.alloc()); // We propagate the SslHandler via an AtomicReference to the outer-scope as using // pipeline.get(...) may return null if the pipeline was teared down by the time we call it. // This will happen if the channel was closed in the meantime. sslHandlerRef.set(handler); ch.pipeline().addLast(handler); } }); cc = b.connect(sc.localAddress()).syncUninterruptibly().channel(); SslHandler handler = sslHandlerRef.get(); handler.handshakeFuture().awaitUninterruptibly(); assertFalse(handler.handshakeFuture().isSuccess()); cc.closeFuture().syncUninterruptibly(); } finally { if (cc != null) { cc.close().syncUninterruptibly(); } if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); ReferenceCountUtil.release(sslServerCtx); ReferenceCountUtil.release(sslClientCtx); } } @Test public void testOutboundClosedAfterChannelInactive() throws Exception { SslContext context = SslContextBuilder.forClient().build(); SSLEngine engine = context.newEngine(UnpooledByteBufAllocator.DEFAULT); EmbeddedChannel channel = new EmbeddedChannel(); assertFalse(channel.finish()); channel.pipeline().addLast(new SslHandler(engine)); assertFalse(engine.isOutboundDone()); channel.close().syncUninterruptibly(); assertTrue(engine.isOutboundDone()); } @Test @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) public void testHandshakeFailedByWriteBeforeChannelActive() throws Exception { final SslContext sslClientCtx = SslContextBuilder.forClient() .protocols(SslProtocols.SSL_v3) .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(SslProvider.JDK).build(); EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; Channel cc = null; final CountDownLatch activeLatch = new CountDownLatch(1); final AtomicReference errorRef = new AtomicReference(); final SslHandler sslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); try { sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInboundHandlerAdapter()) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); cc = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(sslHandler); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof AssertionError) { errorRef.set((AssertionError) cause); } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { activeLatch.countDown(); } }); } }).connect(sc.localAddress()).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { // Write something to trigger the handshake before fireChannelActive is called. future.channel().writeAndFlush(wrappedBuffer(new byte [] { 1, 2, 3, 4 })); } }).syncUninterruptibly().channel(); // Ensure there is no AssertionError thrown by having the handshake failed by the writeAndFlush(...) before // channelActive(...) was called. Let's first wait for the activeLatch countdown to happen and after this // check if we saw and AssertionError (even if we timed out waiting). activeLatch.await(5, TimeUnit.SECONDS); AssertionError error = errorRef.get(); if (error != null) { throw error; } assertThat(sslHandler.handshakeFuture().await().cause(), CoreMatchers.instanceOf(SSLException.class)); } finally { if (cc != null) { cc.close().syncUninterruptibly(); } if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); ReferenceCountUtil.release(sslClientCtx); } } @Test @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) public void testHandshakeTimeoutFlushStartsHandshake() throws Exception { testHandshakeTimeout0(false); } @Test @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) public void testHandshakeTimeoutStartTLS() throws Exception { testHandshakeTimeout0(true); } private static void testHandshakeTimeout0(final boolean startTls) throws Exception { final SslContext sslClientCtx = SslContextBuilder.forClient() .startTls(true) .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(SslProvider.JDK).build(); EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; Channel cc = null; final SslHandler sslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); sslHandler.setHandshakeTimeout(500, TimeUnit.MILLISECONDS); try { sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInboundHandlerAdapter()) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); ChannelFuture future = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(sslHandler); if (startTls) { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(wrappedBuffer(new byte[] { 1, 2, 3, 4 })); } }); } } }).connect(sc.localAddress()); if (!startTls) { future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { // Write something to trigger the handshake before fireChannelActive is called. future.channel().writeAndFlush(wrappedBuffer(new byte [] { 1, 2, 3, 4 })); } }); } cc = future.syncUninterruptibly().channel(); Throwable cause = sslHandler.handshakeFuture().await().cause(); assertThat(cause, CoreMatchers.instanceOf(SSLException.class)); assertThat(cause.getMessage(), containsString(""timed out"")); } finally { if (cc != null) { cc.close().syncUninterruptibly(); } if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); ReferenceCountUtil.release(sslClientCtx); } } @Test public void testHandshakeWithExecutorThatExecuteDirectlyJDK() throws Throwable { testHandshakeWithExecutor(DIRECT_EXECUTOR, SslProvider.JDK, false); } @Test public void testHandshakeWithImmediateExecutorJDK() throws Throwable { testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, SslProvider.JDK, false); } @Test public void testHandshakeWithImmediateEventExecutorJDK() throws Throwable { testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, SslProvider.JDK, false); } @Test public void testHandshakeWithExecutorJDK() throws Throwable { ExecutorService executorService = Executors.newCachedThreadPool(); try { testHandshakeWithExecutor(executorService, SslProvider.JDK, false); } finally { executorService.shutdown(); } } @Test public void testHandshakeWithExecutorThatExecuteDirectlyOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); testHandshakeWithExecutor(DIRECT_EXECUTOR, SslProvider.OPENSSL, false); } @Test public void testHandshakeWithImmediateExecutorOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, SslProvider.OPENSSL, false); } @Test public void testHandshakeWithImmediateEventExecutorOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, SslProvider.OPENSSL, false); } @Test public void testHandshakeWithExecutorOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); ExecutorService executorService = Executors.newCachedThreadPool(); try { testHandshakeWithExecutor(executorService, SslProvider.OPENSSL, false); } finally { executorService.shutdown(); } } @Test public void testHandshakeMTLSWithExecutorThatExecuteDirectlyJDK() throws Throwable { testHandshakeWithExecutor(DIRECT_EXECUTOR, SslProvider.JDK, true); } @Test public void testHandshakeMTLSWithImmediateExecutorJDK() throws Throwable { testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, SslProvider.JDK, true); } @Test public void testHandshakeMTLSWithImmediateEventExecutorJDK() throws Throwable { testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, SslProvider.JDK, true); } @Test public void testHandshakeMTLSWithExecutorJDK() throws Throwable { ExecutorService executorService = Executors.newCachedThreadPool(); try { testHandshakeWithExecutor(executorService, SslProvider.JDK, true); } finally { executorService.shutdown(); } } @Test public void testHandshakeMTLSWithExecutorThatExecuteDirectlyOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); testHandshakeWithExecutor(DIRECT_EXECUTOR, SslProvider.OPENSSL, true); } @Test public void testHandshakeMTLSWithImmediateExecutorOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, SslProvider.OPENSSL, true); } @Test public void testHandshakeMTLSWithImmediateEventExecutorOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, SslProvider.OPENSSL, true); } @Test public void testHandshakeMTLSWithExecutorOpenSsl() throws Throwable { OpenSsl.ensureAvailability(); ExecutorService executorService = Executors.newCachedThreadPool(); try { testHandshakeWithExecutor(executorService, SslProvider.OPENSSL, true); } finally { executorService.shutdown(); } } private static void testHandshakeWithExecutor(Executor executor, SslProvider provider, boolean mtls) throws Throwable { final SelfSignedCertificate cert = new SelfSignedCertificate(); final SslContext sslClientCtx; final SslContext sslServerCtx; if (mtls) { sslClientCtx = SslContextBuilder.forClient().protocols(SslProtocols.TLS_v1_2) .trustManager(InsecureTrustManagerFactory.INSTANCE).keyManager(cert.key(), cert.cert()) .sslProvider(provider).build(); sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()).protocols(SslProtocols.TLS_v1_2) .trustManager(InsecureTrustManagerFactory.INSTANCE) .clientAuth(ClientAuth.REQUIRE) .sslProvider(provider).build(); } else { sslClientCtx = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(provider).build(); sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()) .sslProvider(provider).build(); } EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; Channel cc = null; final SslHandler clientSslHandler = new SslHandler( sslClientCtx.newEngine(UnpooledByteBufAllocator.DEFAULT), executor); final SslHandler serverSslHandler = new SslHandler( sslServerCtx.newEngine(UnpooledByteBufAllocator.DEFAULT), executor); final AtomicReference causeRef = new AtomicReference(); try { sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(serverSslHandler); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { causeRef.compareAndSet(null, cause); } }); } }) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); ChannelFuture future = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(clientSslHandler); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { causeRef.compareAndSet(null, cause); } }); } }).connect(sc.localAddress()); cc = future.syncUninterruptibly().channel(); assertTrue(clientSslHandler.handshakeFuture().await().isSuccess()); assertTrue(serverSslHandler.handshakeFuture().await().isSuccess()); Throwable cause = causeRef.get(); if (cause != null) { throw cause; } } finally { if (cc != null) { cc.close().syncUninterruptibly(); } if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); ReferenceCountUtil.release(sslClientCtx); } } @Test public void testClientHandshakeTimeoutBecauseExecutorNotExecute() throws Exception { testHandshakeTimeoutBecauseExecutorNotExecute(true); } @Test public void testServerHandshakeTimeoutBecauseExecutorNotExecute() throws Exception { testHandshakeTimeoutBecauseExecutorNotExecute(false); } private static void testHandshakeTimeoutBecauseExecutorNotExecute(final boolean client) throws Exception { final SslContext sslClientCtx = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(SslProvider.JDK).build(); final SelfSignedCertificate cert = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()) .sslProvider(SslProvider.JDK).build(); EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; Channel cc = null; final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT, new Executor() { @Override public void execute(Runnable command) { if (!client) { command.run(); } // Do nothing to simulate slow execution. } }); if (client) { clientSslHandler.setHandshakeTimeout(100, TimeUnit.MILLISECONDS); } final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT, new Executor() { @Override public void execute(Runnable command) { if (client) { command.run(); } // Do nothing to simulate slow execution. } }); if (!client) { serverSslHandler.setHandshakeTimeout(100, TimeUnit.MILLISECONDS); } try { sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(serverSslHandler) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); ChannelFuture future = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(clientSslHandler); } }).connect(sc.localAddress()); cc = future.syncUninterruptibly().channel(); if (client) { Throwable cause = clientSslHandler.handshakeFuture().await().cause(); assertThat(cause, CoreMatchers.instanceOf(SslHandshakeTimeoutException.class)); assertFalse(serverSslHandler.handshakeFuture().await().isSuccess()); } else { Throwable cause = serverSslHandler.handshakeFuture().await().cause(); assertThat(cause, CoreMatchers.instanceOf(SslHandshakeTimeoutException.class)); assertFalse(clientSslHandler.handshakeFuture().await().isSuccess()); } } finally { if (cc != null) { cc.close().syncUninterruptibly(); } if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); ReferenceCountUtil.release(sslClientCtx); } } @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testSessionTicketsWithTLSv12() throws Throwable { testSessionTickets(SslProvider.OPENSSL, SslProtocols.TLS_v1_2, true); } @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testSessionTicketsWithTLSv13() throws Throwable { assumeTrue(SslProvider.isTlsv13Supported(SslProvider.OPENSSL)); testSessionTickets(SslProvider.OPENSSL, SslProtocols.TLS_v1_3, true); } @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testSessionTicketsWithTLSv12AndNoKey() throws Throwable { testSessionTickets(SslProvider.OPENSSL, SslProtocols.TLS_v1_2, false); } @Test @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) public void testSessionTicketsWithTLSv13AndNoKey() throws Throwable { assumeTrue(OpenSsl.isTlsv13Supported()); testSessionTickets(SslProvider.OPENSSL, SslProtocols.TLS_v1_3, false); } private static void testSessionTickets(SslProvider provider, String protocol, boolean withKey) throws Throwable { OpenSsl.ensureAvailability(); final SslContext sslClientCtx = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .sslProvider(provider) .protocols(protocol) .build(); // Explicit enable session cache as it's disabled by default atm. ((OpenSslContext) sslClientCtx).sessionContext() .setSessionCacheEnabled(true); final SelfSignedCertificate cert = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()) .sslProvider(provider) .protocols(protocol) .build(); if (withKey) { OpenSslSessionTicketKey key = new OpenSslSessionTicketKey(new byte[OpenSslSessionTicketKey.NAME_SIZE], new byte[OpenSslSessionTicketKey.HMAC_KEY_SIZE], new byte[OpenSslSessionTicketKey.AES_KEY_SIZE]); ((OpenSslSessionContext) sslClientCtx.sessionContext()).setTicketKeys(key); ((OpenSslSessionContext) sslServerCtx.sessionContext()).setTicketKeys(key); } else { ((OpenSslSessionContext) sslClientCtx.sessionContext()).setTicketKeys(); ((OpenSslSessionContext) sslServerCtx.sessionContext()).setTicketKeys(); } EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; final byte[] bytes = new byte[96]; PlatformDependent.threadLocalRandom().nextBytes(bytes); try { final AtomicReference assertErrorRef = new AtomicReference(); sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { final SslHandler sslHandler = sslServerCtx.newHandler(ch.alloc()); ch.pipeline().addLast(sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT)); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { private int handshakeCount; @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof SslHandshakeCompletionEvent) { handshakeCount++; ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine(); // This test only works for non TLSv1.3 as TLSv1.3 will establish sessions after // the handshake is done. // See https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_sess_set_get_cb.html if (!SslProtocols.TLS_v1_3.equals(engine.getSession().getProtocol())) { // First should not re-use the session try { assertEquals(handshakeCount > 1, engine.isSessionReused()); } catch (AssertionError error) { assertErrorRef.set(error); return; } } ctx.writeAndFlush(Unpooled.wrappedBuffer(bytes)); } } }); } }) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); InetSocketAddress serverAddr = (InetSocketAddress) sc.localAddress(); testSessionTickets(serverAddr, group, sslClientCtx, bytes, false); testSessionTickets(serverAddr, group, sslClientCtx, bytes, true); AssertionError error = assertErrorRef.get(); if (error != null) { throw error; } } finally { if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); ReferenceCountUtil.release(sslClientCtx); } } private static void testSessionTickets(InetSocketAddress serverAddress, EventLoopGroup group, SslContext sslClientCtx, final byte[] bytes, boolean isReused) throws Throwable { Channel cc = null; final BlockingQueue queue = new LinkedBlockingQueue(); try { final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT, serverAddress.getAddress().getHostAddress(), serverAddress.getPort()); ChannelFuture future = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(clientSslHandler); ch.pipeline().addLast(new ByteToMessageDecoder() { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { if (in.readableBytes() == bytes.length) { queue.add(in.readBytes(bytes.length)); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { queue.add(cause); } }); } }).connect(serverAddress); cc = future.syncUninterruptibly().channel(); assertTrue(clientSslHandler.handshakeFuture().sync().isSuccess()); ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) clientSslHandler.engine(); // This test only works for non TLSv1.3 as TLSv1.3 will establish sessions after // the handshake is done. // See https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_sess_set_get_cb.html if (!SslProtocols.TLS_v1_3.equals(engine.getSession().getProtocol())) { assertEquals(isReused, engine.isSessionReused()); } Object obj = queue.take(); if (obj instanceof ByteBuf) { ByteBuf buffer = (ByteBuf) obj; ByteBuf expected = Unpooled.wrappedBuffer(bytes); try { assertEquals(expected, buffer); } finally { expected.release(); buffer.release(); } } else { throw (Throwable) obj; } } finally { if (cc != null) { cc.close().syncUninterruptibly(); } } } @Test @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) public void testHandshakeFailureOnlyFireExceptionOnce() throws Exception { final SslContext sslClientCtx = SslContextBuilder.forClient() .trustManager(new X509ExtendedTrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { failVerification(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { failVerification(); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { failVerification(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { failVerification(); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { failVerification(); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { failVerification(); } @Override public X509Certificate[] getAcceptedIssuers() { return EmptyArrays.EMPTY_X509_CERTIFICATES; } private void failVerification() throws CertificateException { throw new CertificateException(); } }) .sslProvider(SslProvider.JDK).build(); final SelfSignedCertificate cert = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()) .sslProvider(SslProvider.JDK).build(); EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); try { final Object terminalEvent = new Object(); final BlockingQueue errorQueue = new LinkedBlockingQueue(); sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(serverSslHandler); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(final ChannelHandlerContext ctx, Throwable cause) { errorQueue.add(cause); } @Override public void channelInactive(ChannelHandlerContext ctx) { errorQueue.add(terminalEvent); } }); } }) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); final ChannelFuture future = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(clientSslHandler); } }).connect(sc.localAddress()); future.syncUninterruptibly(); clientSslHandler.handshakeFuture().addListener(new FutureListener() { @Override public void operationComplete(Future f) { future.channel().close(); } }); assertFalse(clientSslHandler.handshakeFuture().await().isSuccess()); assertFalse(serverSslHandler.handshakeFuture().await().isSuccess()); Object error = errorQueue.take(); assertThat(error, Matchers.instanceOf(DecoderException.class)); assertThat(((Throwable) error).getCause(), Matchers.instanceOf(SSLException.class)); Object terminal = errorQueue.take(); assertSame(terminalEvent, terminal); assertNull(errorQueue.poll(1, TimeUnit.MILLISECONDS)); } finally { if (sc != null) { sc.close().syncUninterruptibly(); } group.shutdownGracefully(); } } @Test public void testHandshakeFailureCipherMissmatchTLSv12Jdk() throws Exception { testHandshakeFailureCipherMissmatch(SslProvider.JDK, false); } @Test public void testHandshakeFailureCipherMissmatchTLSv13Jdk() throws Exception { assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK)); testHandshakeFailureCipherMissmatch(SslProvider.JDK, true); } @Test public void testHandshakeFailureCipherMissmatchTLSv12OpenSsl() throws Exception { OpenSsl.ensureAvailability(); testHandshakeFailureCipherMissmatch(SslProvider.OPENSSL, false); } @Test public void testHandshakeFailureCipherMissmatchTLSv13OpenSsl() throws Exception { OpenSsl.ensureAvailability(); assumeTrue(SslProvider.isTlsv13Supported(SslProvider.OPENSSL)); assumeFalse(OpenSsl.isBoringSSL(), ""BoringSSL does not support setting ciphers for TLSv1.3 explicit""); testHandshakeFailureCipherMissmatch(SslProvider.OPENSSL, true); } private static void testHandshakeFailureCipherMissmatch(SslProvider provider, boolean tls13) throws Exception { final String clientCipher; final String serverCipher; final String protocol; if (tls13) { clientCipher = ""TLS_AES_128_GCM_SHA256""; serverCipher = ""TLS_AES_256_GCM_SHA384""; protocol = SslProtocols.TLS_v1_3; } else { clientCipher = ""TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256""; serverCipher = ""TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384""; protocol = SslProtocols.TLS_v1_2; } final SslContext sslClientCtx = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .protocols(protocol) .ciphers(Collections.singleton(clientCipher)) .sslProvider(provider).build(); final SelfSignedCertificate cert = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()) .protocols(protocol) .ciphers(Collections.singleton(serverCipher)) .sslProvider(provider).build(); EventLoopGroup group = new NioEventLoopGroup(); Channel sc = null; Channel cc = null; final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); class SslEventHandler extends ChannelInboundHandlerAdapter { private final AtomicReference ref; SslEventHandler(AtomicReference ref) { this.ref = ref; } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof SslHandshakeCompletionEvent) { ref.set((SslHandshakeCompletionEvent) evt); } super.userEventTriggered(ctx, evt); } } final AtomicReference clientEvent = new AtomicReference(); final AtomicReference serverEvent = new AtomicReference(); try { sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(serverSslHandler); ch.pipeline().addLast(new SslEventHandler(serverEvent)); } }) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); ChannelFuture future = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(clientSslHandler); ch.pipeline().addLast(new SslEventHandler(clientEvent)); } }).connect(sc.localAddress()); cc = future.syncUninterruptibly().channel(); Throwable clientCause = clientSslHandler.handshakeFuture().await().cause(); assertThat(clientCause, CoreMatchers.instanceOf(SSLException.class)); assertThat(clientCause.getCause(), not(CoreMatchers.instanceOf(ClosedChannelException.class))); Throwable serverCause = serverSslHandler.handshakeFuture().await().cause(); assertThat(serverCause, CoreMatchers.instanceOf(SSLException.class)); assertThat(serverCause.getCause(), not(CoreMatchers.instanceOf(ClosedChannelException.class))); cc.close().syncUninterruptibly(); sc.close().syncUninterruptibly(); Throwable eventClientCause = clientEvent.get().cause(); assertThat(eventClientCause, CoreMatchers.instanceOf(SSLException.class)); assertThat(eventClientCause.getCause(), not(CoreMatchers.instanceOf(ClosedChannelException.class))); Throwable serverEventCause = serverEvent.get().cause(); assertThat(serverEventCause, CoreMatchers.instanceOf(SSLException.class)); assertThat(serverEventCause.getCause(), not(CoreMatchers.instanceOf(ClosedChannelException.class))); } finally { group.shutdownGracefully(); ReferenceCountUtil.release(sslClientCtx); } } @Test public void testSslCompletionEventsTls12JDK() throws Exception { testSslCompletionEvents(SslProvider.JDK, SslProtocols.TLS_v1_2, true); testSslCompletionEvents(SslProvider.JDK, SslProtocols.TLS_v1_2, false); } @Test public void testSslCompletionEventsTls12Openssl() throws Exception { OpenSsl.ensureAvailability(); testSslCompletionEvents(SslProvider.OPENSSL, SslProtocols.TLS_v1_2, true); testSslCompletionEvents(SslProvider.OPENSSL, SslProtocols.TLS_v1_2, false); } @Test public void testSslCompletionEventsTls13JDK() throws Exception { assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK)); testSslCompletionEvents(SslProvider.JDK, SslProtocols.TLS_v1_3, true); testSslCompletionEvents(SslProvider.JDK, SslProtocols.TLS_v1_3, false); } @Test public void testSslCompletionEventsTls13Openssl() throws Exception { OpenSsl.ensureAvailability(); assumeTrue(SslProvider.isTlsv13Supported(SslProvider.OPENSSL)); testSslCompletionEvents(SslProvider.OPENSSL, SslProtocols.TLS_v1_3, true); testSslCompletionEvents(SslProvider.OPENSSL, SslProtocols.TLS_v1_3, false); } private void testSslCompletionEvents(SslProvider provider, final String protocol, boolean clientClose) throws Exception { final SslContext sslClientCtx = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .protocols(protocol) .sslProvider(provider).build(); final SelfSignedCertificate cert = new SelfSignedCertificate(); final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()) .protocols(protocol) .sslProvider(provider).build(); EventLoopGroup group = new NioEventLoopGroup(); final LinkedBlockingQueue acceptedChannels = new LinkedBlockingQueue(); final LinkedBlockingQueue serverHandshakeCompletionEvents = new LinkedBlockingQueue(); final LinkedBlockingQueue [MASK] = new LinkedBlockingQueue(); final LinkedBlockingQueue serverCloseCompletionEvents = new LinkedBlockingQueue(); final LinkedBlockingQueue clientCloseCompletionEvents = new LinkedBlockingQueue(); try { Channel sc = new ServerBootstrap() .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { acceptedChannels.add(ch); SslHandler handler = sslServerCtx.newHandler(ch.alloc()); if (!SslProtocols.TLS_v1_3.equals(protocol)) { handler.setCloseNotifyReadTimeout(5, TimeUnit.SECONDS); } ch.pipeline().addLast(handler); ch.pipeline().addLast(new SslCompletionEventHandler( serverHandshakeCompletionEvents, serverCloseCompletionEvents)); } }) .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); Bootstrap bs = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { SslHandler handler = sslClientCtx.newHandler( ch.alloc(), ""netty.io"", 9999); if (!SslProtocols.TLS_v1_3.equals(protocol)) { handler.setCloseNotifyReadTimeout(5, TimeUnit.SECONDS); } ch.pipeline().addLast(handler); ch.pipeline().addLast( new SslCompletionEventHandler( [MASK] , clientCloseCompletionEvents)); } }) .remoteAddress(sc.localAddress()); Channel cc1 = bs.connect().sync().channel(); Channel cc2 = bs.connect().sync().channel(); // We expect 4 events as we have 2 connections and for each connection there should be one event // on the server-side and one on the client-side. for (int i = 0; i < 2; i++) { SslHandshakeCompletionEvent event = [MASK] .take(); assertTrue(event.isSuccess()); } for (int i = 0; i < 2; i++) { SslHandshakeCompletionEvent event = serverHandshakeCompletionEvents.take(); assertTrue(event.isSuccess()); } assertEquals(0, clientCloseCompletionEvents.size()); assertEquals(0, serverCloseCompletionEvents.size()); if (clientClose) { cc1.close().sync(); cc2.close().sync(); acceptedChannels.take().closeFuture().sync(); acceptedChannels.take().closeFuture().sync(); } else { acceptedChannels.take().close().sync(); acceptedChannels.take().close().sync(); cc1.closeFuture().sync(); cc2.closeFuture().sync(); } // We expect 4 events as we have 2 connections and for each connection there should be one event // on the server-side and one on the client-side. for (int i = 0; i < 2; i++) { SslCloseCompletionEvent event = clientCloseCompletionEvents.take(); if (clientClose) { // When we use TLSv1.3 the remote peer is not required to send a close_notify as response. // See: // - https://datatracker.ietf.org/doc/html/rfc8446#section-6.1 // - https://bugs.openjdk.org/browse/JDK-8208526 if (SslProtocols.TLS_v1_3.equals(protocol)) { assertNotNull(event); } else { assertTrue(event.isSuccess()); } } else { assertTrue(event.isSuccess()); } } for (int i = 0; i < 2; i++) { SslCloseCompletionEvent event = serverCloseCompletionEvents.take(); if (clientClose) { assertTrue(event.isSuccess()); } else { // When we use TLSv1.3 the remote peer is not required to send a close_notify as response. // See: // - https://datatracker.ietf.org/doc/html/rfc8446#section-6.1 // - https://bugs.openjdk.org/browse/JDK-8208526 if (SslProtocols.TLS_v1_3.equals(protocol)) { assertNotNull(event); } else { assertTrue(event.isSuccess()); } } } sc.close().sync(); assertEquals(0, [MASK] .size()); assertEquals(0, serverHandshakeCompletionEvents.size()); assertEquals(0, clientCloseCompletionEvents.size()); assertEquals(0, serverCloseCompletionEvents.size()); } finally { group.shutdownGracefully(); ReferenceCountUtil.release(sslClientCtx); ReferenceCountUtil.release(sslServerCtx); } } private static class SslCompletionEventHandler extends ChannelInboundHandlerAdapter { private final Queue handshakeCompletionEvents; private final Queue closeCompletionEvents; SslCompletionEventHandler(Queue handshakeCompletionEvents, Queue closeCompletionEvents) { this.handshakeCompletionEvents = handshakeCompletionEvents; this.closeCompletionEvents = closeCompletionEvents; } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof SslHandshakeCompletionEvent) { handshakeCompletionEvents.add((SslHandshakeCompletionEvent) evt); } else if (evt instanceof SslCloseCompletionEvent) { closeCompletionEvents.add((SslCloseCompletionEvent) evt); } } @Override public boolean isSharable() { return true; } } } ","clientHandshakeCompletionEvents " "package jadx.tests.integration.others; import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.Test; import jadx.api.data.ICodeComment; import jadx.api.data.IJavaCodeRef; import jadx.api.data.IJavaNodeRef.RefType; import jadx.api.data.impl.JadxCodeComment; import jadx.api.data.impl.JadxCodeData; import jadx.api.data.impl.JadxCodeRef; import jadx.api.data.impl.JadxNodeRef; import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestCodeComments extends IntegrationTest { @SuppressWarnings(""FieldCanBeLocal"") public static class TestCls { private int intField = 5; public static class A { } public int test() { System.out.println(""Hello""); System.out.println(""comment""); return intField; } } @Test public void test() { String baseClsId = TestCls.class.getName(); ICodeComment clsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId), ""class comment""); ICodeComment innerClsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId + ""$A""), ""inner class comment""); ICodeComment fldComment = new JadxCodeComment(new JadxNodeRef(RefType.FIELD, baseClsId, ""intField:I""), ""field comment""); JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, ""test()I""); ICodeComment mthComment = new JadxCodeComment(mthRef, ""method comment""); IJavaCodeRef insnRef = JadxCodeRef.forInsn(isJavaInput() ? 13 : 11); ICodeComment insnComment = new JadxCodeComment(mthRef, insnRef, ""insn comment""); JadxCodeData [MASK] = new JadxCodeData(); getArgs().setCodeData( [MASK] ); [MASK] .setComments(Arrays.asList(clsComment, innerClsComment, fldComment, mthComment, insnComment)); ClassNode cls = getClassNode(TestCls.class); assertThat(cls) .decompile() .checkCodeOffsets() .code() .containsOne(""// class comment"") .containsOne(""// inner class comment"") .containsOne(""// field comment"") .containsOne(""// method comment"") .containsOne(""System.out.println(\""comment\""); // insn comment""); String code = cls.getCode().getCodeStr(); assertThat(cls) .reloadCode(this) .isEqualTo(code); ICodeComment updInsnComment = new JadxCodeComment(mthRef, insnRef, ""updated insn comment""); [MASK] .setComments(Collections.singletonList(updInsnComment)); jadxDecompiler.reloadCodeData(); assertThat(cls) .reloadCode(this) .containsOne(""System.out.println(\""comment\""); // updated insn comment"") .doesNotContain(""class comment"") .containsOne("" comment""); } } ","codeData " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 2005-2016, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ package android.icu.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Arrays; import java.util.Date; import java.util.MissingResourceException; import android.icu.util.AnnualTimeZoneRule; import android.icu.util.BasicTimeZone; import android.icu.util.Calendar; import android.icu.util.DateTimeRule; import android.icu.util.GregorianCalendar; import android.icu.util.InitialTimeZoneRule; import android.icu.util.SimpleTimeZone; import android.icu.util.TimeArrayTimeZoneRule; import android.icu.util.TimeZone; import android.icu.util.TimeZoneRule; import android.icu.util.TimeZoneTransition; import android.icu.util.UResourceBundle; /** * A time zone based on the Olson tz database. Olson time zones change * behavior over time. The raw offset, rules, presence or absence of * daylight savings time, and even the daylight savings amount can all * vary. * * This class uses a resource bundle named ""zoneinfo"". Zoneinfo is a * table containing different kinds of resources. In several places, * zones are referred to using integers. A zone's integer is a number * from 0..n-1, where n is the number of zones, with the zones sorted * in lexicographic order. * * 1. Zones. These have keys corresponding to the Olson IDs, e.g., * ""Asia/Shanghai"". Each resource describes the behavior of the given * zone. Zones come in two different formats. * * a. Zone (table). A zone is a table resource contains several * type of resources below: * * - typeOffsets:intvector (Required) * * Sets of UTC raw/dst offset pairs in seconds. Entries at * 2n represents raw offset and 2n+1 represents dst offset * paired with the raw offset at 2n. The very first pair represents * the initial zone offset (before the first transition) always. * * - trans:intvector (Optional) * * List of transition times represented by 32bit seconds from the * epoch (1970-01-01T00:00Z) in ascending order. * * - transPre32/transPost32:intvector (Optional) * * List of transition times before/after 32bit minimum seconds. * Each time is represented by a pair of 32bit integer. * * - typeMap:bin (Optional) * * Array of bytes representing the mapping between each transition * time (transPre32/trans/transPost32) and its corresponding offset * data (typeOffsets). * * - finalRule:string (Optional) * * If a recurrent transition rule is applicable to a zone forever * after the final transition time, finalRule represents the rule * in Rules data. * * - finalRaw:int (Optional) * * When finalRule is available, finalRaw is required and specifies * the raw (base) offset of the rule. * * - finalYear:int (Optional) * * When finalRule is available, finalYear is required and specifies * the start year of the rule. * * - links:intvector (Optional) * * When this zone data is shared with other zones, links specifies * all zones including the zone itself. Each zone is referenced by * integer index. * * b. Link (int, length 1). A link zone is an int resource. The * integer is the zone number of the target zone. The key of this * resource is an alternate name for the target zone. This data * is corresponding to Link data in the tz database. * * * 2. Rules. These have keys corresponding to the Olson rule IDs, * with an underscore prepended, e.g., ""_EU"". Each resource describes * the behavior of the given rule using an intvector, containing the * onset list, the cessation list, and the DST savings. The onset and * cessation lists consist of the month, dowim, dow, time, and time * mode. The end result is that the 11 integers describing the rule * can be passed directly into the SimpleTimeZone 13-argument * constructor (the other two arguments will be the raw offset, taken * from the complex zone element 5, and the ID string, which is not * used), with the times and the DST savings multiplied by 1000 to * scale from seconds to milliseconds. * * 3. Regions. An array specifies mapping between zones and regions. * Each item is either a 2-letter ISO country code or ""001"" * (UN M.49 - World). This data is generated from ""zone.tab"" * in the tz database. * @hide Only a subset of ICU is exposed in Android */ public class OlsonTimeZone extends BasicTimeZone { // Generated by serialver from JDK 1.4.1_01 static final long serialVersionUID = -6281977362477515376L; /* (non-Javadoc) * @see android.icu.util.TimeZone#getOffset(int, int, int, int, int, int) */ @Override public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) { if (month < Calendar.JANUARY || month > Calendar.DECEMBER) { throw new IllegalArgumentException(""Month is not in the legal range: "" +month); } else { return getOffset(era, year, month, day, dayOfWeek, milliseconds, Grego.monthLength(year, month)); } } /** * TimeZone API. */ public int getOffset(int era, int year, int month,int dom, int dow, int millis, int monthLength){ if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC) || month < Calendar.JANUARY || month > Calendar.DECEMBER || dom < 1 || dom > monthLength || dow < Calendar.SUNDAY || dow > Calendar.SATURDAY || millis < 0 || millis >= Grego.MILLIS_PER_DAY || monthLength < 28 || monthLength > 31) { throw new IllegalArgumentException(); } if (era == GregorianCalendar.BC) { year = -year; } if (finalZone != null && year >= finalStartYear) { return finalZone.getOffset(era, year, month, dom, dow, millis); } // Compute local epoch millis from input fields long time = Grego.fieldsToDay(year, month, dom) * Grego.MILLIS_PER_DAY + millis; int[] offsets = new int[2]; getHistoricalOffset(time, true, LOCAL_DST, LOCAL_STD, offsets); return offsets[0] + offsets[1]; } /* (non-Javadoc) * @see android.icu.util.TimeZone#setRawOffset(int) */ @Override public void setRawOffset(int offsetMillis) { if (isFrozen()) { throw new UnsupportedOperationException(""Attempt to modify a frozen OlsonTimeZone instance.""); } if (getRawOffset() == offsetMillis) { return; } long current = System.currentTimeMillis(); if (current < finalStartMillis) { SimpleTimeZone stz = new SimpleTimeZone(offsetMillis, getID()); boolean bDst = useDaylightTime(); if (bDst) { TimeZoneRule[] currentRules = getSimpleTimeZoneRulesNear(current); if (currentRules.length != 3) { // DST was observed at the beginning of this year, so useDaylightTime // returned true. getSimpleTimeZoneRulesNear requires at least one // future transition for making a pair of rules. This implementation // rolls back the time before the latest offset transition. TimeZoneTransition tzt = getPreviousTransition(current, false); if (tzt != null) { currentRules = getSimpleTimeZoneRulesNear(tzt.getTime() - 1); } } if (currentRules.length == 3 && (currentRules[1] instanceof AnnualTimeZoneRule) && (currentRules[2] instanceof AnnualTimeZoneRule)) { // A pair of AnnualTimeZoneRule AnnualTimeZoneRule r1 = (AnnualTimeZoneRule)currentRules[1]; AnnualTimeZoneRule r2 = (AnnualTimeZoneRule)currentRules[2]; DateTimeRule start, end; int offset1 = r1.getRawOffset() + r1.getDSTSavings(); int offset2 = r2.getRawOffset() + r2.getDSTSavings(); int sav; if (offset1 > offset2) { start = r1.getRule(); end = r2.getRule(); sav = offset1 - offset2; } else { start = r2.getRule(); end = r1.getRule(); sav = offset2 - offset1; } // getSimpleTimeZoneRulesNear always return rules using DOW / WALL_TIME stz.setStartRule(start.getRuleMonth(), start.getRuleWeekInMonth(), start.getRuleDayOfWeek(), start.getRuleMillisInDay()); stz.setEndRule(end.getRuleMonth(), end.getRuleWeekInMonth(), end.getRuleDayOfWeek(), end.getRuleMillisInDay()); // set DST saving amount and start year stz.setDSTSavings(sav); } else { // This could only happen if last rule is DST // and the rule used forever. For example, Asia/Dhaka // in tzdata2009i stays in DST forever. // Hack - set DST starting at midnight on Jan 1st, // ending 23:59:59.999 on Dec 31st stz.setStartRule(0, 1, 0); stz.setEndRule(11, 31, Grego.MILLIS_PER_DAY - 1); } } int[] fields = Grego.timeToFields(current, null); finalStartYear = fields[0]; finalStartMillis = Grego.fieldsToDay(fields[0], 0, 1); if (bDst) { // we probably do not need to set start year of final rule // to finalzone itself, but we always do this for now. stz.setStartYear(finalStartYear); } finalZone = stz; } else { finalZone.setRawOffset(offsetMillis); } transitionRulesInitialized = false; } @Override public Object clone() { if (isFrozen()) { return this; } return cloneAsThawed(); } /** * TimeZone API. */ @Override public void getOffset(long date, boolean local, int[] offsets) { if (finalZone != null && date >= finalStartMillis) { finalZone.getOffset(date, local, offsets); } else { getHistoricalOffset(date, local, LOCAL_FORMER, LOCAL_LATTER, offsets); } } /** * {@inheritDoc} */ @Override public void getOffsetFromLocal(long date, int [MASK] , int duplicatedTimeOpt, int[] offsets) { if (finalZone != null && date >= finalStartMillis) { finalZone.getOffsetFromLocal(date, [MASK] , duplicatedTimeOpt, offsets); } else { getHistoricalOffset(date, true, [MASK] , duplicatedTimeOpt, offsets); } } /* (non-Javadoc) * @see android.icu.util.TimeZone#getRawOffset() */ @Override public int getRawOffset() { int[] ret = new int[2]; getOffset(System.currentTimeMillis(), false, ret); return ret[0]; } /* (non-Javadoc) * @see android.icu.util.TimeZone#useDaylightTime() */ @Override public boolean useDaylightTime() { // If DST was observed in 1942 (for example) but has never been // observed from 1943 to the present, most clients will expect // this method to return FALSE. This method determines whether // DST is in use in the current year (at any point in the year) // and returns TRUE if so. long current = System.currentTimeMillis(); if (finalZone != null && current >= finalStartMillis) { return (finalZone != null && finalZone.useDaylightTime()); } int[] fields = Grego.timeToFields(current, null); // Find start of this year, and start of next year long start = Grego.fieldsToDay(fields[0], 0, 1) * SECONDS_PER_DAY; long limit = Grego.fieldsToDay(fields[0] + 1, 0, 1) * SECONDS_PER_DAY; // Return TRUE if DST is observed at any time during the current // year. for (int i = 0; i < transitionCount; ++i) { if (transitionTimes64[i] >= limit) { break; } if ((transitionTimes64[i] >= start && dstOffsetAt(i) != 0) || (transitionTimes64[i] > start && i > 0 && dstOffsetAt(i - 1) != 0)) { return true; } } return false; } /* (non-Javadoc) * @see android.icu.util.TimeZone#observesDaylightTime() */ @Override public boolean observesDaylightTime() { long current = System.currentTimeMillis(); if (finalZone != null) { if (finalZone.useDaylightTime()) { return true; } else if (current >= finalStartMillis) { return false; } } // Return TRUE if DST is observed at any future time long currentSec = Grego.floorDivide(current, Grego.MILLIS_PER_SECOND); int trsIdx = transitionCount - 1; if (dstOffsetAt(trsIdx) != 0) { return true; } while (trsIdx >= 0) { if (transitionTimes64[trsIdx] <= currentSec) { break; } if (dstOffsetAt(trsIdx - 1) != 0) { return true; } trsIdx--; } return false; } /** * TimeZone API * Returns the amount of time to be added to local standard time * to get local wall clock time. */ @Override public int getDSTSavings() { if (finalZone != null){ return finalZone.getDSTSavings(); } return super.getDSTSavings(); } /* (non-Javadoc) * @see android.icu.util.TimeZone#inDaylightTime(java.util.Date) */ @Override public boolean inDaylightTime(Date date) { int[] temp = new int[2]; getOffset(date.getTime(), false, temp); return temp[1] != 0; } /* (non-Javadoc) * @see android.icu.util.TimeZone#hasSameRules(android.icu.util.TimeZone) */ @Override public boolean hasSameRules(TimeZone other) { if (this == other) { return true; } // The super class implementation only check raw offset and // use of daylight saving time. if (!super.hasSameRules(other)) { return false; } if (!(other instanceof OlsonTimeZone)) { // We cannot reasonably compare rules in different types return false; } // Check final zone OlsonTimeZone o = (OlsonTimeZone)other; if (finalZone == null) { if (o.finalZone != null) { return false; } } else { if (o.finalZone == null || finalStartYear != o.finalStartYear || !(finalZone.hasSameRules(o.finalZone))) { return false; } } // Check transitions // Note: The code below actually fails to compare two equivalent rules in // different representation properly. if (transitionCount != o.transitionCount || !Arrays.equals(transitionTimes64, o.transitionTimes64) || typeCount != o.typeCount || !Arrays.equals(typeMapData, o.typeMapData) || !Arrays.equals(typeOffsets, o.typeOffsets)){ return false; } return true; } /** * Returns the canonical ID of this system time zone */ public String getCanonicalID() { if (canonicalID == null) { synchronized(this) { if (canonicalID == null) { canonicalID = getCanonicalID(getID()); assert(canonicalID != null); if (canonicalID == null) { // This should never happen... canonicalID = getID(); } } } } return canonicalID; } /** * Construct a GMT+0 zone with no transitions. This is done when a * constructor fails so the resultant object is well-behaved. */ private void constructEmpty(){ transitionCount = 0; transitionTimes64 = null; typeMapData = null; typeCount = 1; typeOffsets = new int[]{0,0}; finalZone = null; finalStartYear = Integer.MAX_VALUE; finalStartMillis = Double.MAX_VALUE; transitionRulesInitialized = false; } /** * Construct from a resource bundle * @param top the top-level zoneinfo resource bundle. This is used * to lookup the rule that `res' may refer to, if there is one. * @param res the resource bundle of the zone to be constructed * @param id time zone ID */ public OlsonTimeZone(UResourceBundle top, UResourceBundle res, String id){ super(id); construct(top, res); } private void construct(UResourceBundle top, UResourceBundle res){ if ((top == null || res == null)) { throw new IllegalArgumentException(); } if(DEBUG) System.out.println(""OlsonTimeZone("" + res.getKey() +"")""); UResourceBundle r; int[] transPre32, trans32, transPost32; transPre32 = trans32 = transPost32 = null; transitionCount = 0; // Pre-32bit second transitions try { r = res.get(""transPre32""); transPre32 = r.getIntVector(); if (transPre32.length % 2 != 0) { // elements in the pre-32bit must be an even number throw new IllegalArgumentException(""Invalid Format""); } transitionCount += transPre32.length / 2; } catch (MissingResourceException e) { // Pre-32bit transition data is optional } // 32bit second transitions try { r = res.get(""trans""); trans32 = r.getIntVector(); transitionCount += trans32.length; } catch (MissingResourceException e) { // 32bit transition data is optional } // Post-32bit second transitions try { r = res.get(""transPost32""); transPost32 = r.getIntVector(); if (transPost32.length % 2 != 0) { // elements in the post-32bit must be an even number throw new IllegalArgumentException(""Invalid Format""); } transitionCount += transPost32.length / 2; } catch (MissingResourceException e) { // Post-32bit transition data is optional } if (transitionCount > 0) { transitionTimes64 = new long[transitionCount]; int idx = 0; if (transPre32 != null) { for (int i = 0; i < transPre32.length / 2; i++, idx++) { transitionTimes64[idx] = ((transPre32[i * 2]) & 0x00000000FFFFFFFFL) << 32 | ((transPre32[i * 2 + 1]) & 0x00000000FFFFFFFFL); } } if (trans32 != null) { for (int i = 0; i < trans32.length; i++, idx++) { transitionTimes64[idx] = trans32[i]; } } if (transPost32 != null) { for (int i = 0; i < transPost32.length / 2; i++, idx++) { transitionTimes64[idx] = ((transPost32[i * 2]) & 0x00000000FFFFFFFFL) << 32 | ((transPost32[i * 2 + 1]) & 0x00000000FFFFFFFFL); } } } else { transitionTimes64 = null; } // Type offsets list must be of even size, with size >= 2 r = res.get(""typeOffsets""); typeOffsets = r.getIntVector(); if ((typeOffsets.length < 2 || typeOffsets.length > 0x7FFE || typeOffsets.length % 2 != 0)) { throw new IllegalArgumentException(""Invalid Format""); } typeCount = typeOffsets.length / 2; // Type map data must be of the same size as the transition count if (transitionCount > 0) { r = res.get(""typeMap""); typeMapData = r.getBinary(null); if (typeMapData == null || typeMapData.length != transitionCount) { throw new IllegalArgumentException(""Invalid Format""); } } else { typeMapData = null; } // Process final rule and data, if any finalZone = null; finalStartYear = Integer.MAX_VALUE; finalStartMillis = Double.MAX_VALUE; String ruleID = null; try { ruleID = res.getString(""finalRule""); r = res.get(""finalRaw""); int ruleRaw = r.getInt() * Grego.MILLIS_PER_SECOND; r = loadRule(top, ruleID); int[] ruleData = r.getIntVector(); if (ruleData == null || ruleData.length != 11) { throw new IllegalArgumentException(""Invalid Format""); } finalZone = new SimpleTimeZone(ruleRaw, """", ruleData[0], ruleData[1], ruleData[2], ruleData[3] * Grego.MILLIS_PER_SECOND, ruleData[4], ruleData[5], ruleData[6], ruleData[7], ruleData[8] * Grego.MILLIS_PER_SECOND, ruleData[9], ruleData[10] * Grego.MILLIS_PER_SECOND); r = res.get(""finalYear""); finalStartYear = r.getInt(); // Note: Setting finalStartYear to the finalZone is problematic. When a date is around // year boundary, SimpleTimeZone may return false result when DST is observed at the // beginning of year. We could apply safe margin (day or two), but when one of recurrent // rules falls around year boundary, it could return false result. Without setting the // start year, finalZone works fine around the year boundary of the start year. // finalZone.setStartYear(finalStartYear); // Compute the millis for Jan 1, 0:00 GMT of the finalYear // Note: finalStartMillis is used for detecting either if // historic transition data or finalZone to be used. In an // extreme edge case - for example, two transitions fall into // small windows of time around the year boundary, this may // result incorrect offset computation. But I think it will // never happen practically. Yoshito - Feb 20, 2010 finalStartMillis = Grego.fieldsToDay(finalStartYear, 0, 1) * Grego.MILLIS_PER_DAY; } catch (MissingResourceException e) { if (ruleID != null) { // ruleID is found, but missing other data required for // creating finalZone throw new IllegalArgumentException(""Invalid Format""); } } } // This constructor is used for testing purpose only public OlsonTimeZone(String id){ super(id); UResourceBundle top = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER); UResourceBundle res = ZoneMeta.openOlsonResource(top, id); construct(top, res); if (finalZone != null){ finalZone.setID(id); } } /* (non-Javadoc) * @see android.icu.util.TimeZone#setID(java.lang.String) */ @Override public void setID(String id){ if (isFrozen()) { throw new UnsupportedOperationException(""Attempt to modify a frozen OlsonTimeZone instance.""); } // Before updating the ID, preserve the original ID's canonical ID. if (canonicalID == null) { canonicalID = getCanonicalID(getID()); assert(canonicalID != null); if (canonicalID == null) { // This should never happen... canonicalID = getID(); } } if (finalZone != null){ finalZone.setID(id); } super.setID(id); transitionRulesInitialized = false; } // Maximum absolute offset in seconds = 1 day. // getHistoricalOffset uses this constant as safety margin of // quick zone transition checking. private static final int MAX_OFFSET_SECONDS = 86400; // 60 * 60 * 24; private void getHistoricalOffset(long date, boolean local, int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets) { if (transitionCount != 0) { long sec = Grego.floorDivide(date, Grego.MILLIS_PER_SECOND); if (!local && sec < transitionTimes64[0]) { // Before the first transition time offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; } else { // Linear search from the end is the fastest approach, since // most lookups will happen at/near the end. int transIdx; for (transIdx = transitionCount - 1; transIdx >= 0; transIdx--) { long transition = transitionTimes64[transIdx]; if (local && (sec >= (transition - MAX_OFFSET_SECONDS))) { int offsetBefore = zoneOffsetAt(transIdx - 1); boolean dstBefore = dstOffsetAt(transIdx - 1) != 0; int offsetAfter = zoneOffsetAt(transIdx); boolean dstAfter = dstOffsetAt(transIdx) != 0; boolean dstToStd = dstBefore && !dstAfter; boolean stdToDst = !dstBefore && dstAfter; if (offsetAfter - offsetBefore >= 0) { // Positive transition, which makes a non-existing local time range if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { transition += offsetBefore; } else if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { transition += offsetAfter; } else if ((NonExistingTimeOpt & FORMER_LATTER_MASK) == LOCAL_LATTER) { transition += offsetBefore; } else { // Interprets the time with rule before the transition, // default for non-existing time range transition += offsetAfter; } } else { // Negative transition, which makes a duplicated local time range if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { transition += offsetAfter; } else if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { transition += offsetBefore; } else if ((DuplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) { transition += offsetBefore; } else { // Interprets the time with rule after the transition, // default for duplicated local time range transition += offsetAfter; } } } if (sec >= transition) { break; } } // transIdx could be -1 when local=true offsets[0] = rawOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; offsets[1] = dstOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; } } else { // No transitions, single pair of offsets only offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; } } private int getInt(byte val){ return val & 0xFF; } /* * Following 3 methods return an offset at the given transition time index. * When the index is negative, return the initial offset. */ private int zoneOffsetAt(int transIdx) { int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; return typeOffsets[typeIdx] + typeOffsets[typeIdx + 1]; } private int rawOffsetAt(int transIdx) { int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; return typeOffsets[typeIdx]; } private int dstOffsetAt(int transIdx) { int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; return typeOffsets[typeIdx + 1]; } private int initialRawOffset() { return typeOffsets[0]; } private int initialDstOffset() { return typeOffsets[1]; } // temp @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(super.toString()); buf.append('['); buf.append(""transitionCount="" + transitionCount); buf.append("",typeCount="" + typeCount); buf.append("",transitionTimes=""); if (transitionTimes64 != null) { buf.append('['); for (int i = 0; i < transitionTimes64.length; ++i) { if (i > 0) { buf.append(','); } buf.append(Long.toString(transitionTimes64[i])); } buf.append(']'); } else { buf.append(""null""); } buf.append("",typeOffsets=""); if (typeOffsets != null) { buf.append('['); for (int i = 0; i < typeOffsets.length; ++i) { if (i > 0) { buf.append(','); } buf.append(Integer.toString(typeOffsets[i])); } buf.append(']'); } else { buf.append(""null""); } buf.append("",typeMapData=""); if (typeMapData != null) { buf.append('['); for (int i = 0; i < typeMapData.length; ++i) { if (i > 0) { buf.append(','); } buf.append(Byte.toString(typeMapData[i])); } } else { buf.append(""null""); } buf.append("",finalStartYear="" + finalStartYear); buf.append("",finalStartMillis="" + finalStartMillis); buf.append("",finalZone="" + finalZone); buf.append(']'); return buf.toString(); } /** * Number of transitions, 0..~370 */ private int transitionCount; /** * Number of types, 1..255 */ private int typeCount; /** * Time of each transition in seconds from 1970 epoch. */ private long[] transitionTimes64; /** * Offset from GMT in seconds for each type. * Length is equal to typeCount */ private int[] typeOffsets; /** * Type description data, consisting of transitionCount uint8_t * type indices (from 0..typeCount-1). * Length is equal to transitionCount */ private byte[] typeMapData; /** * For year >= finalStartYear, the finalZone will be used. */ private int finalStartYear = Integer.MAX_VALUE; /** * For date >= finalStartMillis, the finalZone will be used. */ private double finalStartMillis = Double.MAX_VALUE; /** * A SimpleTimeZone that governs the behavior for years >= finalYear. * If and only if finalYear == INT32_MAX then finalZone == 0. */ private SimpleTimeZone finalZone = null; // owned, may be NULL /** * The canonical ID of this zone. Initialized when {@link #getCanonicalID()} * is invoked first time, or {@link #setID(String)} is called. */ private volatile String canonicalID = null; private static final String ZONEINFORES = ""zoneinfo64""; private static final boolean DEBUG = ICUDebug.enabled(""olson""); private static final int SECONDS_PER_DAY = 24*60*60; private static UResourceBundle loadRule(UResourceBundle top, String ruleid) { UResourceBundle r = top.get(""Rules""); r = r.get(ruleid); return r; } @Override public boolean equals(Object obj){ if (!super.equals(obj)) return false; // super does class check OlsonTimeZone z = (OlsonTimeZone) obj; return (Utility.arrayEquals(typeMapData, z.typeMapData) || // If the pointers are not equal, the zones may still // be equal if their rules and transitions are equal (finalStartYear == z.finalStartYear && // Don't compare finalMillis; if finalYear is ==, so is finalMillis ((finalZone == null && z.finalZone == null) || (finalZone != null && z.finalZone != null && finalZone.equals(z.finalZone)) && transitionCount == z.transitionCount && typeCount == z.typeCount && Utility.arrayEquals(transitionTimes64, z.transitionTimes64) && Utility.arrayEquals(typeOffsets, z.typeOffsets) && Utility.arrayEquals(typeMapData, z.typeMapData) ))); } @Override public int hashCode(){ int ret = (int) (finalStartYear ^ (finalStartYear>>>4) + transitionCount ^ (transitionCount>>>6) + typeCount ^ (typeCount>>>8) + Double.doubleToLongBits(finalStartMillis)+ (finalZone == null ? 0 : finalZone.hashCode()) + super.hashCode()); if (transitionTimes64 != null) { for(int i=0; i>>8); } } for(int i=0; i>>8); } if (typeMapData != null) { for(int i=0; i= firstFinalTZTransition.getTime()) { if (finalZone.useDaylightTime()) { //return finalZone.getNextTransition(base, inclusive); return finalZoneWithStartYear.getNextTransition(base, inclusive); } else { // No more transitions return null; } } } if (historicRules != null) { // Find a historical transition int ttidx = transitionCount - 1; for (; ttidx >= firstTZTransitionIdx; ttidx--) { long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; if (base > t || (!inclusive && base == t)) { break; } } if (ttidx == transitionCount - 1) { return firstFinalTZTransition; } else if (ttidx < firstTZTransitionIdx) { return firstTZTransition; } else { // Create a TimeZoneTransition TimeZoneRule to = historicRules[getInt(typeMapData[ttidx + 1])]; TimeZoneRule from = historicRules[getInt(typeMapData[ttidx])]; long startTime = transitionTimes64[ttidx+1] * Grego.MILLIS_PER_SECOND; // The transitions loaded from zoneinfo.res may contain non-transition data if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() && from.getDSTSavings() == to.getDSTSavings()) { return getNextTransition(startTime, false); } return new TimeZoneTransition(startTime, from, to); } } return null; } /* (non-Javadoc) * @see android.icu.util.BasicTimeZone#getPreviousTransition(long, boolean) */ @Override public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) { initTransitionRules(); if (finalZone != null) { if (inclusive && base == firstFinalTZTransition.getTime()) { return firstFinalTZTransition; } else if (base > firstFinalTZTransition.getTime()) { if (finalZone.useDaylightTime()) { //return finalZone.getPreviousTransition(base, inclusive); return finalZoneWithStartYear.getPreviousTransition(base, inclusive); } else { return firstFinalTZTransition; } } } if (historicRules != null) { // Find a historical transition int ttidx = transitionCount - 1; for (; ttidx >= firstTZTransitionIdx; ttidx--) { long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; if (base > t || (inclusive && base == t)) { break; } } if (ttidx < firstTZTransitionIdx) { // No more transitions return null; } else if (ttidx == firstTZTransitionIdx) { return firstTZTransition; } else { // Create a TimeZoneTransition TimeZoneRule to = historicRules[getInt(typeMapData[ttidx])]; TimeZoneRule from = historicRules[getInt(typeMapData[ttidx-1])]; long startTime = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; // The transitions loaded from zoneinfo.res may contain non-transition data if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() && from.getDSTSavings() == to.getDSTSavings()) { return getPreviousTransition(startTime, false); } return new TimeZoneTransition(startTime, from, to); } } return null; } /* (non-Javadoc) * @see android.icu.util.BasicTimeZone#getTimeZoneRules() */ @Override public TimeZoneRule[] getTimeZoneRules() { initTransitionRules(); int size = 1; if (historicRules != null) { // historicRules may contain null entries when original zoneinfo data // includes non transition data. for (int i = 0; i < historicRules.length; i++) { if (historicRules[i] != null) { size++; } } } if (finalZone != null) { if (finalZone.useDaylightTime()) { size += 2; } else { size++; } } TimeZoneRule[] rules = new TimeZoneRule[size]; int idx = 0; rules[idx++] = initialRule; if (historicRules != null) { for (int i = 0; i < historicRules.length; i++) { if (historicRules[i] != null) { rules[idx++] = historicRules[i]; } } } if (finalZone != null) { if (finalZone.useDaylightTime()) { TimeZoneRule[] stzr = finalZoneWithStartYear.getTimeZoneRules(); // Adding only transition rules rules[idx++] = stzr[1]; rules[idx++] = stzr[2]; } else { // Create a TimeArrayTimeZoneRule at finalMillis rules[idx++] = new TimeArrayTimeZoneRule(getID() + ""(STD)"", finalZone.getRawOffset(), 0, new long[] {(long)finalStartMillis}, DateTimeRule.UTC_TIME); } } return rules; } private transient InitialTimeZoneRule initialRule; private transient TimeZoneTransition firstTZTransition; private transient int firstTZTransitionIdx; private transient TimeZoneTransition firstFinalTZTransition; private transient TimeArrayTimeZoneRule[] historicRules; private transient SimpleTimeZone finalZoneWithStartYear; // hack private transient boolean transitionRulesInitialized; private synchronized void initTransitionRules() { if (transitionRulesInitialized) { return; } initialRule = null; firstTZTransition = null; firstFinalTZTransition = null; historicRules = null; firstTZTransitionIdx = 0; finalZoneWithStartYear = null; String stdName = getID() + ""(STD)""; String dstName = getID() + ""(DST)""; int raw, dst; // Create initial rule raw = initialRawOffset() * Grego.MILLIS_PER_SECOND; dst = initialDstOffset() * Grego.MILLIS_PER_SECOND; initialRule = new InitialTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst); if (transitionCount > 0) { int transitionIdx, typeIdx; // We probably no longer need to check the first ""real"" transition // here, because the new tzcode remove such transitions already. // For now, keeping this code for just in case. Feb 19, 2010 Yoshito for (transitionIdx = 0; transitionIdx < transitionCount; transitionIdx++) { if (getInt(typeMapData[transitionIdx]) != 0) { // type 0 is the initial type break; } firstTZTransitionIdx++; } if (transitionIdx == transitionCount) { // Actually no transitions... } else { // Build historic rule array long[] times = new long[transitionCount]; for (typeIdx = 0; typeIdx < typeCount; typeIdx++) { // Gather all start times for each pair of offsets int nTimes = 0; for (transitionIdx = firstTZTransitionIdx; transitionIdx < transitionCount; transitionIdx++) { if (typeIdx == getInt(typeMapData[transitionIdx])) { long tt = transitionTimes64[transitionIdx] * Grego.MILLIS_PER_SECOND; if (tt < finalStartMillis) { // Exclude transitions after finalMillis times[nTimes++] = tt; } } } if (nTimes > 0) { long[] startTimes = new long[nTimes]; System.arraycopy(times, 0, startTimes, 0, nTimes); // Create a TimeArrayTimeZoneRule raw = typeOffsets[typeIdx*2]*Grego.MILLIS_PER_SECOND; dst = typeOffsets[typeIdx*2 + 1]*Grego.MILLIS_PER_SECOND; if (historicRules == null) { historicRules = new TimeArrayTimeZoneRule[typeCount]; } historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst, startTimes, DateTimeRule.UTC_TIME); } } // Create initial transition typeIdx = getInt(typeMapData[firstTZTransitionIdx]); firstTZTransition = new TimeZoneTransition(transitionTimes64[firstTZTransitionIdx] * Grego.MILLIS_PER_SECOND, initialRule, historicRules[typeIdx]); } } if (finalZone != null) { // Get the first occurrence of final rule starts long startTime = (long)finalStartMillis; TimeZoneRule firstFinalRule; if (finalZone.useDaylightTime()) { /* * Note: When an OlsonTimeZone is constructed, we should set the final year * as the start year of finalZone. However, the boundary condition used for * getting offset from finalZone has some problems. So setting the start year * in the finalZone will cause a problem. For now, we do not set the valid * start year when the construction time and create a clone and set the * start year when extracting rules. */ finalZoneWithStartYear = (SimpleTimeZone)finalZone.clone(); finalZoneWithStartYear.setStartYear(finalStartYear); TimeZoneTransition tzt = finalZoneWithStartYear.getNextTransition(startTime, false); firstFinalRule = tzt.getTo(); startTime = tzt.getTime(); } else { finalZoneWithStartYear = finalZone; firstFinalRule = new TimeArrayTimeZoneRule(finalZone.getID(), finalZone.getRawOffset(), 0, new long[] {startTime}, DateTimeRule.UTC_TIME); } TimeZoneRule prevRule = null; if (transitionCount > 0) { prevRule = historicRules[getInt(typeMapData[transitionCount - 1])]; } if (prevRule == null) { // No historic transitions, but only finalZone available prevRule = initialRule; } firstFinalTZTransition = new TimeZoneTransition(startTime, prevRule, firstFinalRule); } transitionRulesInitialized = true; } // Note: This class does not support back level serialization compatibility // very well. ICU 4.4 introduced the 64bit transition data. It is probably // possible to implement this class to make old version of ICU to deserialize // object stream serialized by ICU 4.4+. However, such implementation will // introduce unnecessary complexity other than serialization support. // I decided to provide minimum level of backward compatibility, which // only support ICU 4.4+ to create an instance of OlsonTimeZone by reloading // the zone rules from bundles. ICU 4.2 or older version of ICU cannot // deserialize object stream created by ICU 4.4+. Yoshito -Feb 22, 2010 private static final int currentSerialVersion = 1; private int serialVersionOnStream = currentSerialVersion; private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); if (serialVersionOnStream < 1) { // No version - 4.2 or older // Just reloading the rule from bundle boolean initialized = false; String tzid = getID(); if (tzid != null) { try { UResourceBundle top = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER); UResourceBundle res = ZoneMeta.openOlsonResource(top, tzid); construct(top, res); if (finalZone != null){ finalZone.setID(tzid); } initialized = true; } catch (Exception ignored) { // throw away } } if (!initialized) { // final resort constructEmpty(); } } // need to rebuild transition rules when requested transitionRulesInitialized = false; } // Freezable stuffs private transient volatile boolean isFrozen = false; /* (non-Javadoc) * @see android.icu.util.TimeZone#isFrozen() */ @Override public boolean isFrozen() { return isFrozen; } /* (non-Javadoc) * @see android.icu.util.TimeZone#freeze() */ @Override public TimeZone freeze() { isFrozen = true; return this; } /* (non-Javadoc) * @see android.icu.util.TimeZone#cloneAsThawed() */ @Override public TimeZone cloneAsThawed() { OlsonTimeZone tz = (OlsonTimeZone)super.cloneAsThawed(); if (finalZone != null) { // TODO Do we really need this? finalZone.setID(getID()); tz.finalZone = (SimpleTimeZone) finalZone.clone(); } // Following data are read-only and never changed. // Therefore, shallow copies should be sufficient. // // transitionTimes64 // typeMapData // typeOffsets tz.isFrozen = false; return tz; } } ","nonExistingTimeOpt " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /***************************************************************************************** * * Copyright (C) 1996-2012, International Business Machines * Corporation and others. All Rights Reserved. **/ /** * Port From: JDK 1.4b1 : java.text.Format.IntlTestDecimalFormatAPI * Source File: java/text/format/IntlTestDecimalFormatAPI.java **/ /* @test 1.4 98/03/06 @summary test International Decimal Format API */ package android.icu.dev.test.format; import java.text.FieldPosition; import java.text.Format; import java.text.ParseException; import java.text.ParsePosition; import java.util.Locale; import org.junit.Test; import android.icu.math.BigDecimal; import android.icu.math.MathContext; import android.icu.text.DecimalFormat; import android.icu.text.DecimalFormatSymbols; import android.icu.text.NumberFormat; public class IntlTestDecimalFormatAPI extends android.icu.dev.test.TestFmwk { /** * Problem 1: simply running * decF4.setRoundingMode(java.math.BigDecimal.ROUND_HALF_UP) does not work * as decF4.setRoundingIncrement(.0001) must also be run. * Problem 2: decF4.format(8.88885) does not return 8.8889 as expected. * You must run decF4.format(new BigDecimal(Double.valueOf(8.88885))) in * order for this to work as expected. * Problem 3: There seems to be no way to set half up to be the default * rounding mode. * We solved the problem with the code at the bottom of this page however * this is not quite general purpose enough to include in icu4j. A static * setDefaultRoundingMode function would solve the problem nicely. Also * decimal places past 20 are not handled properly. A small ammount of work * would make bring this up to snuff. */ @Test public void testJB1871() { // problem 2 double number = 8.88885; String expected = ""8.8889""; String pat = "",##0.0000""; DecimalFormat dec = new DecimalFormat(pat); dec.setRoundingMode(BigDecimal.ROUND_HALF_UP); double [MASK] = 0.0001; dec.setRoundingIncrement( [MASK] ); String str = dec.format(number); if (!str.equals(expected)) { errln(""Fail: "" + number + "" x \"""" + pat + ""\"" = \"""" + str + ""\"", expected \"""" + expected + ""\""""); } pat = "",##0.0001""; dec = new DecimalFormat(pat); dec.setRoundingMode(BigDecimal.ROUND_HALF_UP); str = dec.format(number); if (!str.equals(expected)) { errln(""Fail: "" + number + "" x \"""" + pat + ""\"" = \"""" + str + ""\"", expected \"""" + expected + ""\""""); } // testing 20 decimal places pat = "",##0.00000000000000000001""; dec = new DecimalFormat(pat); BigDecimal bignumber = new BigDecimal(""8.888888888888888888885""); expected = ""8.88888888888888888889""; dec.setRoundingMode(BigDecimal.ROUND_HALF_UP); str = dec.format(bignumber); if (!str.equals(expected)) { errln(""Fail: "" + bignumber + "" x \"""" + pat + ""\"" = \"""" + str + ""\"", expected \"""" + expected + ""\""""); } } /** * This test checks various generic API methods in DecimalFormat to achieve * 100% API coverage. */ @Test public void TestAPI() { logln(""DecimalFormat API test---""); logln(""""); Locale.setDefault(Locale.ENGLISH); // ======= Test constructors logln(""Testing DecimalFormat constructors""); DecimalFormat def = new DecimalFormat(); final String pattern = new String(""#,##0.# FF""); DecimalFormat pat = null; try { pat = new DecimalFormat(pattern); } catch (IllegalArgumentException e) { errln(""ERROR: Could not create DecimalFormat (pattern)""); } DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.FRENCH); DecimalFormat cust1 = new DecimalFormat(pattern, symbols); // ======= Test clone(), assignment, and equality logln(""Testing clone() and equality operators""); Format clone = (Format) def.clone(); if( ! def.equals(clone)) { errln(""ERROR: Clone() failed""); } // ======= Test various format() methods logln(""Testing various format() methods""); // final double d = -10456.0037; // this appears as -10456.003700000001 on NT // final double d = -1.04560037e-4; // this appears as -1.0456003700000002E-4 on NT final double d = -10456.00370000000000; // this works! final long l = 100000000; logln("""" + d + "" is the double value""); StringBuffer res1 = new StringBuffer(); StringBuffer res2 = new StringBuffer(); StringBuffer res3 = new StringBuffer(); StringBuffer res4 = new StringBuffer(); FieldPosition pos1 = new FieldPosition(0); FieldPosition pos2 = new FieldPosition(0); FieldPosition pos3 = new FieldPosition(0); FieldPosition pos4 = new FieldPosition(0); res1 = def.format(d, res1, pos1); logln("""" + d + "" formatted to "" + res1); res2 = pat.format(l, res2, pos2); logln("""" + l + "" formatted to "" + res2); res3 = cust1.format(d, res3, pos3); logln("""" + d + "" formatted to "" + res3); res4 = cust1.format(l, res4, pos4); logln("""" + l + "" formatted to "" + res4); // ======= Test parse() logln(""Testing parse()""); String text = new String(""-10,456.0037""); ParsePosition pos = new ParsePosition(0); String patt = new String(""#,##0.#""); pat.applyPattern(patt); double d2 = pat.parse(text, pos).doubleValue(); if(d2 != d) { errln(""ERROR: Roundtrip failed (via parse("" + d2 + "" != "" + d + "")) for "" + text); } logln(text + "" parsed into "" + (long) d2); // ======= Test getters and setters logln(""Testing getters and setters""); final DecimalFormatSymbols syms = pat.getDecimalFormatSymbols(); def.setDecimalFormatSymbols(syms); if( ! pat.getDecimalFormatSymbols().equals(def.getDecimalFormatSymbols())) { errln(""ERROR: set DecimalFormatSymbols() failed""); } String posPrefix; pat.setPositivePrefix(""+""); posPrefix = pat.getPositivePrefix(); logln(""Positive prefix (should be +): "" + posPrefix); if(posPrefix != ""+"") { errln(""ERROR: setPositivePrefix() failed""); } String negPrefix; pat.setNegativePrefix(""-""); negPrefix = pat.getNegativePrefix(); logln(""Negative prefix (should be -): "" + negPrefix); if(negPrefix != ""-"") { errln(""ERROR: setNegativePrefix() failed""); } String posSuffix; pat.setPositiveSuffix(""_""); posSuffix = pat.getPositiveSuffix(); logln(""Positive suffix (should be _): "" + posSuffix); if(posSuffix != ""_"") { errln(""ERROR: setPositiveSuffix() failed""); } String negSuffix; pat.setNegativeSuffix(""~""); negSuffix = pat.getNegativeSuffix(); logln(""Negative suffix (should be ~): "" + negSuffix); if(negSuffix != ""~"") { errln(""ERROR: setNegativeSuffix() failed""); } long multiplier = 0; pat.setMultiplier(8); multiplier = pat.getMultiplier(); logln(""Multiplier (should be 8): "" + multiplier); if(multiplier != 8) { errln(""ERROR: setMultiplier() failed""); } int groupingSize = 0; pat.setGroupingSize(2); groupingSize = pat.getGroupingSize(); logln(""Grouping size (should be 2): "" + (long) groupingSize); if(groupingSize != 2) { errln(""ERROR: setGroupingSize() failed""); } pat.setDecimalSeparatorAlwaysShown(true); boolean tf = pat.isDecimalSeparatorAlwaysShown(); logln(""DecimalSeparatorIsAlwaysShown (should be true) is "" + (tf ? ""true"" : ""false"")); if(tf != true) { errln(""ERROR: setDecimalSeparatorAlwaysShown() failed""); } String funkyPat; funkyPat = pat.toPattern(); logln(""Pattern is "" + funkyPat); String locPat; locPat = pat.toLocalizedPattern(); logln(""Localized pattern is "" + locPat); // ======= Test applyPattern() logln(""Testing applyPattern()""); String p1 = new String(""#,##0.0#;(#,##0.0#)""); logln(""Applying pattern "" + p1); pat.applyPattern(p1); String s2; s2 = pat.toPattern(); logln(""Extracted pattern is "" + s2); if( ! s2.equals(p1) ) { errln(""ERROR: toPattern() result did not match pattern applied""); } String p2 = new String(""#,##0.0# FF;(#,##0.0# FF)""); logln(""Applying pattern "" + p2); pat.applyLocalizedPattern(p2); String s3; s3 = pat.toLocalizedPattern(); logln(""Extracted pattern is "" + s3); if( ! s3.equals(p2) ) { errln(""ERROR: toLocalizedPattern() result did not match pattern applied""); } } @Test public void testJB6134() { DecimalFormat decfmt = new DecimalFormat(); StringBuffer buf = new StringBuffer(); FieldPosition fposByInt = new FieldPosition(NumberFormat.INTEGER_FIELD); decfmt.format(123, buf, fposByInt); buf.setLength(0); FieldPosition fposByField = new FieldPosition(NumberFormat.Field.INTEGER); decfmt.format(123, buf, fposByField); if (fposByInt.getEndIndex() != fposByField.getEndIndex()) { errln(""ERROR: End index for integer field - fposByInt:"" + fposByInt.getEndIndex() + "" / fposByField: "" + fposByField.getEndIndex()); } } @Test public void testJB4971() { DecimalFormat decfmt = new DecimalFormat(); MathContext resultICU; MathContext comp1 = new MathContext(0, MathContext.PLAIN); resultICU = decfmt.getMathContextICU(); if ((comp1.getDigits() != resultICU.getDigits()) || (comp1.getForm() != resultICU.getForm()) || (comp1.getLostDigits() != resultICU.getLostDigits()) || (comp1.getRoundingMode() != resultICU.getRoundingMode())) { errln(""ERROR: Math context 1 not equal - result: "" + resultICU.toString() + "" / expected: "" + comp1.toString()); } MathContext comp2 = new MathContext(5, MathContext.ENGINEERING); decfmt.setMathContextICU(comp2); resultICU = decfmt.getMathContextICU(); if ((comp2.getDigits() != resultICU.getDigits()) || (comp2.getForm() != resultICU.getForm()) || (comp2.getLostDigits() != resultICU.getLostDigits()) || (comp2.getRoundingMode() != resultICU.getRoundingMode())) { errln(""ERROR: Math context 2 not equal - result: "" + resultICU.toString() + "" / expected: "" + comp2.toString()); } java.math.MathContext result; java.math.MathContext comp3 = new java.math.MathContext(3, java.math.RoundingMode.DOWN); decfmt.setMathContext(comp3); result = decfmt.getMathContext(); if ((comp3.getPrecision() != result.getPrecision()) || (comp3.getRoundingMode() != result.getRoundingMode())) { errln(""ERROR: Math context 3 not equal - result: "" + result.toString() + "" / expected: "" + comp3.toString()); } } @Test public void testJB6354() { DecimalFormat pat = new DecimalFormat(""#,##0.00""); java.math.BigDecimal r1, r2; // get default rounding increment r1 = pat.getRoundingIncrement(); // set rounding mode with zero increment. Rounding // increment should be set by this operation pat.setRoundingMode(BigDecimal.ROUND_UP); r2 = pat.getRoundingIncrement(); // check for different values if ((r1 != null) && (r2 != null)) { if (r1.compareTo(r2) == 0) { errln(""ERROR: Rounding increment did not change""); } } } @Test public void testJB6648() { DecimalFormat df = new DecimalFormat(); df.setParseStrict(true); String numstr = new String(); String[] patterns = { ""0"", ""00"", ""000"", ""0,000"", ""0.0"", ""#000.0"" }; for(int i=0; i < patterns.length; i++) { df.applyPattern(patterns[i]); numstr = df.format(5); try { Number n = df.parse(numstr); logln(""INFO: Parsed "" + numstr + "" -> "" + n); } catch (ParseException pe) { errln(""ERROR: Failed round trip with strict parsing.""); } } df.applyPattern(patterns[1]); numstr = ""005""; try { Number n = df.parse(numstr); logln(""INFO: Successful parse for "" + numstr + "" with strict parse enabled. Number is "" + n); } catch (ParseException pe) { errln(""ERROR: Parse Exception encountered in strict mode: numstr -> "" + numstr); } } } ","roundinginc " "/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.imagepipeline.memory; import com.facebook.common.internal.ImmutableMap; import com.facebook.common.references.CloseableReference; import com.facebook.imagepipeline.testing.FakeNativeMemoryChunkPool; import java.util.Arrays; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; /** Tests for {@link MemoryPooledByteBufferOutputStream} */ @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class MemoryPooledByteBufferOutputStreamTest extends TestUsingNativeMemoryChunk { private MemoryChunkPool mNativePool; private byte[] mData; private PoolStats mNativeStats; @Before public void setup() { mNativePool = new FakeNativeMemoryChunkPool(); mNativeStats = new PoolStats(mNativePool); mData = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; } @Test public void testBasic_1() throws Exception { testBasic_1(mNativePool, mNativeStats); } @Test public void testBasic_2() throws Exception { testBasic_2(mNativePool, mNativeStats); } @Test public void testBasic_3() throws Exception { testBasic_3(mNativePool, mNativeStats); } @Test public void testBasic_4() throws Exception { testBasic_4(mNativePool, mNativeStats); } @Test public void testClose() { testClose(mNativePool, mNativeStats); } @Test(expected = MemoryPooledByteBufferOutputStream.InvalidStreamException.class) public void testToByteBufExceptionUsingNativePool() { testToByteBufException(mNativePool); } @Test public void testWriteAfterToByteBuf() throws Exception { testWriteAfterToByteBuf(mNativePool); } private void testBasic_1(final MemoryChunkPool mPool, final PoolStats mStats) throws Exception { MemoryPooledByteBufferOutputStream os1 = new MemoryPooledByteBufferOutputStream(mPool); MemoryPooledByteBuffer sb1 = doWrite(os1, mData); Assert.assertEquals(16, sb1.getCloseableReference().get().getSize()); assertArrayEquals(mData, getBytes(sb1), mData.length); mStats.refresh(); Assert.assertEquals( ImmutableMap.of( 32, new IntPair(0, 0), 16, new IntPair(1, 0), 8, new IntPair(0, 1), 4, new IntPair(0, 1)), mStats.getBucketStats()); } private void testBasic_2(final MemoryChunkPool mPool, final PoolStats mStats) throws Exception { MemoryPooledByteBufferOutputStream os2 = new MemoryPooledByteBufferOutputStream(mPool, 8); MemoryPooledByteBuffer sb2 = doWrite(os2, mData); Assert.assertEquals(16, sb2.getCloseableReference().get().getSize()); assertArrayEquals(mData, getBytes(sb2), mData.length); mStats.refresh(); Assert.assertEquals( ImmutableMap.of( 32, new IntPair(0, 0), 16, new IntPair(1, 0), 8, new IntPair(0, 1), 4, new IntPair(0, 0)), mStats.getBucketStats()); } private void testBasic_3(final MemoryChunkPool mPool, final PoolStats mStats) throws Exception { MemoryPooledByteBufferOutputStream os3 = new MemoryPooledByteBufferOutputStream(mPool, 16); MemoryPooledByteBuffer sb3 = doWrite(os3, mData); Assert.assertEquals(16, sb3.getCloseableReference().get().getSize()); assertArrayEquals(mData, getBytes(sb3), mData.length); mStats.refresh(); Assert.assertEquals( ImmutableMap.of( 32, new IntPair(0, 0), 16, new IntPair(1, 0), 8, new IntPair(0, 0), 4, new IntPair(0, 0)), mStats.getBucketStats()); } private void testBasic_4(final MemoryChunkPool mPool, final PoolStats mStats) throws Exception { MemoryPooledByteBufferOutputStream os4 = new MemoryPooledByteBufferOutputStream(mPool, 32); MemoryPooledByteBuffer sb4 = doWrite(os4, mData); Assert.assertEquals(32, sb4.getCloseableReference().get().getSize()); assertArrayEquals(mData, getBytes(sb4), mData.length); mStats.refresh(); Assert.assertEquals( ImmutableMap.of( 32, new IntPair(1, 0), 16, new IntPair(0, 0), 8, new IntPair(0, 0), 4, new IntPair(0, 0)), mStats.getBucketStats()); } private static void testClose(final MemoryChunkPool mPool, final PoolStats mStats) { MemoryPooledByteBufferOutputStream os = new MemoryPooledByteBufferOutputStream(mPool); os.close(); mStats.refresh(); Assert.assertEquals( ImmutableMap.of( 32, new IntPair(0, 0), 16, new IntPair(0, 0), 8, new IntPair(0, 0), 4, new IntPair(0, 1)), mStats.getBucketStats()); } private static void testToByteBufException(final MemoryChunkPool mPool) { MemoryPooledByteBufferOutputStream os1 = new MemoryPooledByteBufferOutputStream(mPool); os1.close(); os1.toByteBuffer(); Assert.fail(); } private void testWriteAfterToByteBuf(final MemoryChunkPool mPool) throws Exception { MemoryPooledByteBufferOutputStream os1 = new MemoryPooledByteBufferOutputStream(mPool); MemoryPooledByteBuffer buf1 = doWrite(os1, Arrays.copyOf(mData, 9)); MemoryPooledByteBuffer buf2 = doWrite(os1, Arrays.copyOf(mData, 3)); Assert.assertEquals(12, buf2.size()); final CloseableReference [MASK] = buf1.getCloseableReference(); Assert.assertEquals(3, [MASK] .getUnderlyingReferenceTestOnly().getRefCountTestOnly()); os1.close(); buf1.close(); buf2.close(); Assert.assertEquals(0, [MASK] .getUnderlyingReferenceTestOnly().getRefCountTestOnly()); } // write out the contents of data into the output stream private static MemoryPooledByteBuffer doWrite(MemoryPooledByteBufferOutputStream os, byte[] data) throws Exception { for (int i = 0; i < data.length; i++) { os.write(data, i, 1); } return os.toByteBuffer(); } // assert that the first 'length' bytes of expected are the same as those in 'actual' private static void assertArrayEquals(byte[] expected, byte[] actual, int length) { Assert.assertTrue(expected.length >= length); Assert.assertTrue(actual.length >= length); for (int i = 0; i < length; i++) { Assert.assertEquals(expected[i], actual[i]); } } private static byte[] getBytes(MemoryPooledByteBuffer bb) { byte[] bytes = new byte[bb.size()]; bb.getCloseableReference().get().read(0, bytes, 0, bytes.length); return bytes; } } ","chunk " "/* * Copyright (C) 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject.spi; import com.google.inject.Key; import com.google.inject.MembersInjector; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matcher; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; /** * Context of an injectable type encounter. Enables reporting errors, registering injection * listeners and binding method interceptors for injectable type {@code I}. It is an error to use an * encounter after the {@link TypeListener#hear(TypeLiteral, TypeEncounter) hear()} method has * returned. * * @param the injectable type encountered * @since 2.0 */ public interface TypeEncounter { /** * Records an error message for type {@code I} which will be presented to the user at a later * time. Unlike throwing an exception, this enable us to continue configuring the Injector and * discover more errors. Uses {@link String#format(String, Object[])} to insert the [MASK] into * the message. */ void addError(String message, Object... [MASK] ); /** * Records an exception for type {@code I}, the full details of which will be logged, and the * message of which will be presented to the user at a later time. If your type listener calls * something that you worry may fail, you should catch the exception and pass it to this method. */ void addError(Throwable t); /** Records an error message to be presented to the user at a later time. */ void addError(Message message); /** * Returns the provider used to obtain instances for the given injection key. The returned * provider will not be valid until the injector has been created. The provider will throw an * {@code IllegalStateException} if you try to use it beforehand. */ Provider getProvider(Key key); /** * Returns the provider used to obtain instances for the given injection type. The returned * provider will not be valid until the injector has been created. The provider will throw an * {@code IllegalStateException} if you try to use it beforehand. */ Provider getProvider(Class type); /** * Returns the members injector used to inject dependencies into methods and fields on instances * of the given type {@code T}. The returned members injector will not be valid until the main * injector has been created. The members injector will throw an {@code IllegalStateException} if * you try to use it beforehand. * * @param typeLiteral type to get members injector for */ MembersInjector getMembersInjector(TypeLiteral typeLiteral); /** * Returns the members injector used to inject dependencies into methods and fields on instances * of the given type {@code T}. The returned members injector will not be valid until the main * injector has been created. The members injector will throw an {@code IllegalStateException} if * you try to use it beforehand. * * @param type type to get members injector for */ MembersInjector getMembersInjector(Class type); /** * Registers a members injector for type {@code I}. Guice will use the members injector after its * performed its own injections on an instance of {@code I}. */ void register(MembersInjector membersInjector); /** * Registers an injection listener for type {@code I}. Guice will notify the listener after all * injections have been performed on an instance of {@code I}. */ void register(InjectionListener listener); /** * Binds method interceptor[s] to methods matched in type {@code I} and its supertypes. A method * is eligible for interception if: * *
    *
  • Guice created the instance the method is on *
  • Neither the enclosing type nor the method is final *
  • And the method is package-private or more accessible *
* * @param methodMatcher matches methods the interceptor should apply to. For example: {@code * annotatedWith(Transactional.class)}. * @param interceptors to bind */ void bindInterceptor(Matcher methodMatcher, MethodInterceptor... interceptors); } ","arguments " "/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.extractor.ts; import static java.lang.Math.min; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.BinarySearchSeeker; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** * A seeker that supports seeking within TS stream using binary search. * *

This seeker uses the first and last PCR values within the stream, as well as the stream * duration to interpolate the PCR value of the seeking position. Then it performs binary search * within the stream to find a packets whose PCR value is within {@link #SEEK_TOLERANCE_US} from the * target PCR. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated /* package */ final class TsBinarySearchSeeker extends BinarySearchSeeker { private static final long SEEK_TOLERANCE_US = 100_000; private static final int MINIMUM_SEARCH_RANGE_BYTES = 5 * TsExtractor.TS_PACKET_SIZE; public TsBinarySearchSeeker( TimestampAdjuster pcrTimestampAdjuster, long streamDurationUs, long inputLength, int pcrPid, int timestampSearchBytes) { super( new DefaultSeekTimestampConverter(), new TsPcrSeeker(pcrPid, pcrTimestampAdjuster, timestampSearchBytes), streamDurationUs, /* floorTimePosition= */ 0, /* ceilingTimePosition= */ streamDurationUs + 1, /* floorBytePosition= */ 0, /* ceilingBytePosition= */ inputLength, /* approxBytesPerFrame= */ TsExtractor.TS_PACKET_SIZE, MINIMUM_SEARCH_RANGE_BYTES); } /** * A {@link TimestampSeeker} implementation that looks for a given PCR timestamp at a given * position in a TS stream. * *

Given a PCR timestamp, and a position within a TS stream, this seeker will peek up to {@link * #timestampSearchBytes} from that stream position, look for all packets with PID equal to * PCR_PID, and then compare the PCR timestamps (if available) of these packets to the target * timestamp. */ private static final class TsPcrSeeker implements TimestampSeeker { private final TimestampAdjuster pcrTimestampAdjuster; private final ParsableByteArray packetBuffer; private final int pcrPid; private final int timestampSearchBytes; public TsPcrSeeker( int pcrPid, TimestampAdjuster pcrTimestampAdjuster, int timestampSearchBytes) { this.pcrPid = pcrPid; this.pcrTimestampAdjuster = pcrTimestampAdjuster; this.timestampSearchBytes = timestampSearchBytes; packetBuffer = new ParsableByteArray(); } @Override public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp) throws IOException { long inputPosition = input.getPosition(); int bytesToSearch = (int) min(timestampSearchBytes, input.getLength() - inputPosition); packetBuffer.reset(bytesToSearch); input.peekFully(packetBuffer.getData(), /* offset= */ 0, bytesToSearch); return searchForPcrValueInBuffer(packetBuffer, targetTimestamp, inputPosition); } private TimestampSearchResult searchForPcrValueInBuffer( ParsableByteArray packetBuffer, long targetPcrTimeUs, long bufferStartOffset) { int limit = packetBuffer.limit(); long startOfLastPacketPosition = C.INDEX_UNSET; long endOfLastPacketPosition = C.INDEX_UNSET; long lastPcrTimeUsInRange = C.TIME_UNSET; while (packetBuffer.bytesLeft() >= TsExtractor.TS_PACKET_SIZE) { int startOfPacket = TsUtil.findSyncBytePosition(packetBuffer.getData(), packetBuffer.getPosition(), limit); int [MASK] = startOfPacket + TsExtractor.TS_PACKET_SIZE; if ( [MASK] > limit) { break; } long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, startOfPacket, pcrPid); if (pcrValue != C.TIME_UNSET) { long pcrTimeUs = pcrTimestampAdjuster.adjustTsTimestamp(pcrValue); if (pcrTimeUs > targetPcrTimeUs) { if (lastPcrTimeUsInRange == C.TIME_UNSET) { // First PCR timestamp is already over target. return TimestampSearchResult.overestimatedResult(pcrTimeUs, bufferStartOffset); } else { // Last PCR timestamp < target timestamp < this timestamp. return TimestampSearchResult.targetFoundResult( bufferStartOffset + startOfLastPacketPosition); } } else if (pcrTimeUs + SEEK_TOLERANCE_US > targetPcrTimeUs) { long startOfPacketInStream = bufferStartOffset + startOfPacket; return TimestampSearchResult.targetFoundResult(startOfPacketInStream); } lastPcrTimeUsInRange = pcrTimeUs; startOfLastPacketPosition = startOfPacket; } packetBuffer.setPosition( [MASK] ); endOfLastPacketPosition = [MASK] ; } if (lastPcrTimeUsInRange != C.TIME_UNSET) { long endOfLastPacketPositionInStream = bufferStartOffset + endOfLastPacketPosition; return TimestampSearchResult.underestimatedResult( lastPcrTimeUsInRange, endOfLastPacketPositionInStream); } else { return TimestampSearchResult.NO_TIMESTAMP_IN_RANGE_RESULT; } } @Override public void onSeekFinished() { packetBuffer.reset(Util.EMPTY_BYTE_ARRAY); } } } ","endOfPacket " "/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.ext.ima; import static com.google.android.exoplayer2.ext.ima.ImaUtil.BITRATE_UNSET; import static com.google.android.exoplayer2.ext.ima.ImaUtil.TIMEOUT_UNSET; import static com.google.android.exoplayer2.ext.ima.ImaUtil.getImaLooper; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import android.content.Context; import android.os.Looper; import android.view.View; import android.view.ViewGroup; import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener; import com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener; import com.google.ads.interactivemedia.v3.api.AdsManager; import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings; import com.google.ads.interactivemedia.v3.api.AdsRequest; import com.google.ads.interactivemedia.v3.api.CompanionAdSlot; import com.google.ads.interactivemedia.v3.api.FriendlyObstruction; import com.google.ads.interactivemedia.v3.api.FriendlyObstructionPurpose; import com.google.ads.interactivemedia.v3.api.ImaSdkFactory; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; import com.google.ads.interactivemedia.v3.api.UiElement; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; /** * {@link AdsLoader} using the IMA SDK. All methods must be called on the main thread. * *

The player instance that will play the loaded ads must be set before playback using {@link * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling * {@link #release()}. * *

See IMA's * Support and compatibility page for information on compatible ad tag formats. Pass the ad tag * URI when setting media item playback properties (if using the media item API) or as a {@link * DataSpec} when constructing the {@link AdsMediaSource} (if using media sources directly). For the * latter case, please note that this implementation delegates loading of the data spec to the IMA * SDK, so range and headers specifications will be ignored in ad tag URIs. Literal ads responses * can be encoded as data scheme data specs, for example, by constructing the data spec using a URI * generated via {@link Util#getDataUriForString(String, String)}. * *

The IMA SDK can report obstructions to the ad view for accurate viewability measurement. This * means that any overlay views that obstruct the ad overlay but are essential for playback need to * be registered via the {@link AdViewProvider} passed to the {@link AdsMediaSource}. See the IMA * SDK Open Measurement documentation for more information. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public final class ImaAdsLoader implements AdsLoader { static { ExoPlayerLibraryInfo.registerModule(""goog.exo.ima""); } /** Builder for {@link ImaAdsLoader}. */ public static final class Builder { /** * The default duration in milliseconds for which the player must buffer while preloading an ad * group before that ad group is skipped and marked as having failed to load. * *

This value should be large enough not to trigger discarding the ad when it actually might * load soon, but small enough so that user is not waiting for too long. * * @see #setAdPreloadTimeoutMs(long) */ public static final long DEFAULT_AD_PRELOAD_TIMEOUT_MS = 10 * C.MILLIS_PER_SECOND; private final Context context; @Nullable private ImaSdkSettings imaSdkSettings; @Nullable private AdErrorListener adErrorListener; @Nullable private AdEventListener adEventListener; @Nullable private VideoAdPlayer.VideoAdPlayerCallback videoAdPlayerCallback; @Nullable private List adMediaMimeTypes; @Nullable private Set adUiElements; @Nullable private Collection companionAdSlots; @Nullable private Boolean enableContinuousPlayback; private long adPreloadTimeoutMs; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; private boolean focusSkipButtonWhenAvailable; private boolean playAdBeforeStartPosition; private boolean debugModeEnabled; private ImaUtil.ImaFactory imaFactory; /** * Creates a new builder for {@link ImaAdsLoader}. * * @param context The context; */ public Builder(Context context) { this.context = checkNotNull(context).getApplicationContext(); adPreloadTimeoutMs = DEFAULT_AD_PRELOAD_TIMEOUT_MS; vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; mediaBitrate = BITRATE_UNSET; focusSkipButtonWhenAvailable = true; playAdBeforeStartPosition = true; imaFactory = new DefaultImaFactory(); } /** * Sets the IMA SDK settings. The provided settings instance's player type and version fields * may be overwritten. * *

If this method is not called the default settings will be used. * * @param imaSdkSettings The {@link ImaSdkSettings}. * @return This builder, for convenience. */ @CanIgnoreReturnValue public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { this.imaSdkSettings = checkNotNull(imaSdkSettings); return this; } /** * Sets a listener for ad errors that will be passed to {@link * com.google.ads.interactivemedia.v3.api.AdsLoader#addAdErrorListener(AdErrorListener)} and * {@link AdsManager#addAdErrorListener(AdErrorListener)}. * * @param adErrorListener The ad error listener. * @return This builder, for convenience. */ @CanIgnoreReturnValue public Builder setAdErrorListener(AdErrorListener adErrorListener) { this.adErrorListener = checkNotNull(adErrorListener); return this; } /** * Sets a listener for ad events that will be passed to {@link * AdsManager#addAdEventListener(AdEventListener)}. * * @param adEventListener The ad event listener. * @return This builder, for convenience. */ @CanIgnoreReturnValue public Builder setAdEventListener(AdEventListener adEventListener) { this.adEventListener = checkNotNull(adEventListener); return this; } /** * Sets a callback to receive video ad player events. Note that these events are handled * internally by the IMA SDK and this ads loader. For analytics and diagnostics, new * implementations should generally use events from the top-level {@link Player} listeners * instead of setting a callback via this method. * * @param videoAdPlayerCallback The callback to receive video ad player events. * @return This builder, for convenience. * @see com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback */ @CanIgnoreReturnValue public Builder setVideoAdPlayerCallback( VideoAdPlayer.VideoAdPlayerCallback videoAdPlayerCallback) { this.videoAdPlayerCallback = checkNotNull(videoAdPlayerCallback); return this; } /** * Sets the ad UI elements to be rendered by the IMA SDK. * * @param adUiElements The ad UI elements to be rendered by the IMA SDK. * @return This builder, for convenience. * @see AdsRenderingSettings#setUiElements(Set) */ @CanIgnoreReturnValue public Builder setAdUiElements(Set adUiElements) { this.adUiElements = ImmutableSet.copyOf(checkNotNull(adUiElements)); return this; } /** * Sets the slots to use for companion ads, if they are present in the loaded ad. * * @param companionAdSlots The slots to use for companion ads. * @return This builder, for convenience. * @see AdDisplayContainer#setCompanionSlots(Collection) */ @CanIgnoreReturnValue public Builder setCompanionAdSlots(Collection companionAdSlots) { this.companionAdSlots = ImmutableList.copyOf(checkNotNull(companionAdSlots)); return this; } /** * Sets the MIME types to prioritize for linear ad media. If not specified, MIME types supported * by the {@link MediaSource.Factory adMediaSourceFactory} used to construct the {@link * AdsMediaSource} will be used. * * @param adMediaMimeTypes The MIME types to prioritize for linear ad media. May contain {@link * MimeTypes#APPLICATION_MPD}, {@link MimeTypes#APPLICATION_M3U8}, {@link * MimeTypes#VIDEO_MP4}, {@link MimeTypes#VIDEO_WEBM}, {@link MimeTypes#VIDEO_H263}, {@link * MimeTypes#AUDIO_MP4} and {@link MimeTypes#AUDIO_MPEG}. * @return This builder, for convenience. * @see AdsRenderingSettings#setMimeTypes(List) */ @CanIgnoreReturnValue public Builder setAdMediaMimeTypes(List adMediaMimeTypes) { this.adMediaMimeTypes = ImmutableList.copyOf(checkNotNull(adMediaMimeTypes)); return this; } /** * Sets whether to enable continuous playback. Pass {@code true} if content videos will be * played continuously, similar to a TV broadcast. This setting may modify the ads request but * does not affect ad playback behavior. The requested value is unknown by default. * * @param enableContinuousPlayback Whether to enable continuous playback. * @return This builder, for convenience. * @see AdsRequest#setContinuousPlayback(boolean) */ @CanIgnoreReturnValue public Builder setEnableContinuousPlayback(boolean enableContinuousPlayback) { this.enableContinuousPlayback = enableContinuousPlayback; return this; } /** * Sets the duration in milliseconds for which the player must buffer while preloading an ad * group before that ad group is skipped and marked as having failed to load. Pass {@link * C#TIME_UNSET} if there should be no such timeout. The default value is {@link * #DEFAULT_AD_PRELOAD_TIMEOUT_MS} ms. * *

The purpose of this timeout is to avoid playback getting stuck in the unexpected case that * the IMA SDK does not load an ad break based on the player's reported content position. * * @param adPreloadTimeoutMs The timeout buffering duration in milliseconds, or {@link * C#TIME_UNSET} for no timeout. * @return This builder, for convenience. */ @CanIgnoreReturnValue public Builder setAdPreloadTimeoutMs(long adPreloadTimeoutMs) { checkArgument(adPreloadTimeoutMs == C.TIME_UNSET || adPreloadTimeoutMs > 0); this.adPreloadTimeoutMs = adPreloadTimeoutMs; return this; } /** * Sets the VAST load timeout, in milliseconds. * * @param vastLoadTimeoutMs The VAST load timeout, in milliseconds. * @return This builder, for convenience. * @see AdsRequest#setVastLoadTimeout(float) */ @CanIgnoreReturnValue public Builder setVastLoadTimeoutMs(@IntRange(from = 1) int vastLoadTimeoutMs) { checkArgument(vastLoadTimeoutMs > 0); this.vastLoadTimeoutMs = vastLoadTimeoutMs; return this; } /** * Sets the ad media load timeout, in milliseconds. * * @param mediaLoadTimeoutMs The ad media load timeout, in milliseconds. * @return This builder, for convenience. * @see AdsRenderingSettings#setLoadVideoTimeout(int) */ @CanIgnoreReturnValue public Builder setMediaLoadTimeoutMs(@IntRange(from = 1) int mediaLoadTimeoutMs) { checkArgument(mediaLoadTimeoutMs > 0); this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; return this; } /** * Sets the media maximum recommended bitrate for ads, in bps. * * @param bitrate The media maximum recommended bitrate for ads, in bps. * @return This builder, for convenience. * @see AdsRenderingSettings#setBitrateKbps(int) */ @CanIgnoreReturnValue public Builder setMaxMediaBitrate(@IntRange(from = 1) int bitrate) { checkArgument(bitrate > 0); this.mediaBitrate = bitrate; return this; } /** * Sets whether to focus the skip button (when available) on Android TV devices. The default * setting is {@code true}. * * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on * Android TV devices. * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ @CanIgnoreReturnValue public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvailable) { this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; return this; } /** * Sets whether to play an ad before the start position when beginning playback. If {@code * true}, an ad will be played if there is one at or before the start position. If {@code * false}, an ad will be played only if there is one exactly at the start position. The default * setting is {@code true}. * * @param playAdBeforeStartPosition Whether to play an ad before the start position when * beginning playback. * @return This builder, for convenience. */ @CanIgnoreReturnValue public Builder setPlayAdBeforeStartPosition(boolean playAdBeforeStartPosition) { this.playAdBeforeStartPosition = playAdBeforeStartPosition; return this; } /** * Sets whether to enable outputting verbose logs for the IMA extension and IMA SDK. The default * value is {@code false}. This setting is intended for debugging only, and should not be * enabled in production applications. * * @param debugModeEnabled Whether to enable outputting verbose logs for the IMA extension and * IMA SDK. * @return This builder, for convenience. * @see ImaSdkSettings#setDebugMode(boolean) */ @CanIgnoreReturnValue public Builder setDebugModeEnabled(boolean debugModeEnabled) { this.debugModeEnabled = debugModeEnabled; return this; } @CanIgnoreReturnValue @VisibleForTesting /* package */ Builder setImaFactory(ImaUtil.ImaFactory imaFactory) { this.imaFactory = checkNotNull(imaFactory); return this; } /** Returns a new {@link ImaAdsLoader}. */ public ImaAdsLoader build() { return new ImaAdsLoader( context, new ImaUtil.Configuration( adPreloadTimeoutMs, vastLoadTimeoutMs, mediaLoadTimeoutMs, focusSkipButtonWhenAvailable, playAdBeforeStartPosition, mediaBitrate, enableContinuousPlayback, adMediaMimeTypes, adUiElements, companionAdSlots, adErrorListener, adEventListener, videoAdPlayerCallback, imaSdkSettings, debugModeEnabled), imaFactory); } } private final ImaUtil.Configuration configuration; private final Context context; private final ImaUtil.ImaFactory imaFactory; private final PlayerListenerImpl playerListener; private final HashMap adTagLoaderByAdsId; private final HashMap adTagLoaderByAdsMediaSource; private final Timeline.Period period; private final Timeline.Window window; private boolean wasSetPlayerCalled; @Nullable private Player nextPlayer; private List supportedMimeTypes; @Nullable private Player player; @Nullable private AdTagLoader currentAdTagLoader; private ImaAdsLoader( Context context, ImaUtil.Configuration configuration, ImaUtil.ImaFactory imaFactory) { this.context = context.getApplicationContext(); this.configuration = configuration; this.imaFactory = imaFactory; playerListener = new PlayerListenerImpl(); supportedMimeTypes = ImmutableList.of(); adTagLoaderByAdsId = new HashMap<>(); adTagLoaderByAdsMediaSource = new HashMap<>(); period = new Timeline.Period(); window = new Timeline.Window(); } /** * Returns the underlying {@link com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this * instance, or {@code null} if ads have not been requested yet. */ @Nullable public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return currentAdTagLoader != null ? currentAdTagLoader.getAdsLoader() : null; } /** * Returns the {@link AdDisplayContainer} used by this loader, or {@code null} if ads have not * been requested yet. * *

Note: any video controls overlays registered via {@link * AdDisplayContainer#registerFriendlyObstruction(FriendlyObstruction)} will be unregistered * automatically when the media source detaches from this instance. It is therefore necessary to * re-register views each time the ads loader is reused. Alternatively, provide overlay views via * the {@link AdViewProvider} when creating the media source to benefit from automatic * registration. */ @Nullable public AdDisplayContainer getAdDisplayContainer() { return currentAdTagLoader != null ? currentAdTagLoader.getAdDisplayContainer() : null; } /** * Requests ads, if they have not already been requested. Must be called on the main thread. * *

Ads will be requested automatically when the player is prepared if this method has not been * called, so it is only necessary to call this method if you want to request ads before preparing * the player. * * @param adTagDataSpec The data specification of the ad tag to load. See class javadoc for * information about compatible ad tag formats. * @param adsId A opaque identifier for the ad playback state across start/stop calls. * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code * null} if playing audio-only ads. */ public void requestAds(DataSpec adTagDataSpec, Object adsId, @Nullable ViewGroup adViewGroup) { if (!adTagLoaderByAdsId.containsKey(adsId)) { AdTagLoader adTagLoader = new AdTagLoader( context, configuration, imaFactory, supportedMimeTypes, adTagDataSpec, adsId, adViewGroup); adTagLoaderByAdsId.put(adsId, adTagLoader); } } /** * Skips the current ad. * *

This method is intended for apps that play audio-only ads and so need to provide their own * UI for users to skip skippable ads. Apps showing video ads should not call this method, as the * IMA SDK provides the UI to skip ads in the ad view group passed via {@link AdViewProvider}. */ public void skipAd() { if (currentAdTagLoader != null) { currentAdTagLoader.skipAd(); } } /** * Moves UI focus to the skip button (or other interactive elements), if currently shown. See * {@link AdsManager#focus()}. */ public void focusSkipButton() { if (currentAdTagLoader != null) { currentAdTagLoader.focusSkipButton(); } } // AdsLoader implementation. @Override public void setPlayer(@Nullable Player player) { checkState(Looper.myLooper() == getImaLooper()); checkState(player == null || player.getApplicationLooper() == getImaLooper()); nextPlayer = player; wasSetPlayerCalled = true; } @Override public void setSupportedContentTypes(@C.ContentType int... contentTypes) { List supportedMimeTypes = new ArrayList<>(); for (@C.ContentType int contentType : contentTypes) { // IMA does not support Smooth Streaming ad media. if (contentType == C.CONTENT_TYPE_DASH) { supportedMimeTypes.add(MimeTypes.APPLICATION_MPD); } else if (contentType == C.CONTENT_TYPE_HLS) { supportedMimeTypes.add(MimeTypes.APPLICATION_M3U8); } else if (contentType == C.CONTENT_TYPE_OTHER) { supportedMimeTypes.addAll( Arrays.asList( MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_WEBM, MimeTypes.VIDEO_H263, MimeTypes.AUDIO_MP4, MimeTypes.AUDIO_MPEG)); } } this.supportedMimeTypes = Collections.unmodifiableList(supportedMimeTypes); } @Override public void start( AdsMediaSource adsMediaSource, DataSpec adTagDataSpec, Object adsId, AdViewProvider adViewProvider, EventListener eventListener) { checkState( wasSetPlayerCalled, ""Set player using adsLoader.setPlayer before preparing the player.""); if (adTagLoaderByAdsMediaSource.isEmpty()) { player = nextPlayer; @Nullable Player player = this.player; if (player == null) { return; } player.addListener(playerListener); } @Nullable AdTagLoader adTagLoader = adTagLoaderByAdsId.get(adsId); if (adTagLoader == null) { requestAds(adTagDataSpec, adsId, adViewProvider.getAdViewGroup()); adTagLoader = adTagLoaderByAdsId.get(adsId); } adTagLoaderByAdsMediaSource.put(adsMediaSource, checkNotNull(adTagLoader)); adTagLoader.addListenerWithAdView(eventListener, adViewProvider); maybeUpdateCurrentAdTagLoader(); } @Override public void stop(AdsMediaSource adsMediaSource, EventListener eventListener) { @Nullable AdTagLoader removedAdTagLoader = adTagLoaderByAdsMediaSource.remove(adsMediaSource); maybeUpdateCurrentAdTagLoader(); if (removedAdTagLoader != null) { removedAdTagLoader.removeListener(eventListener); } if (player != null && adTagLoaderByAdsMediaSource.isEmpty()) { player.removeListener(playerListener); player = null; } } @Override public void release() { if (player != null) { player.removeListener(playerListener); player = null; maybeUpdateCurrentAdTagLoader(); } nextPlayer = null; for (AdTagLoader adTagLoader : adTagLoaderByAdsMediaSource.values()) { adTagLoader.release(); } adTagLoaderByAdsMediaSource.clear(); for (AdTagLoader adTagLoader : adTagLoaderByAdsId.values()) { adTagLoader.release(); } adTagLoaderByAdsId.clear(); } @Override public void handlePrepareComplete( AdsMediaSource adsMediaSource, int adGroupIndex, int adIndexInAdGroup) { if (player == null) { return; } checkNotNull(adTagLoaderByAdsMediaSource.get(adsMediaSource)) .handlePrepareComplete(adGroupIndex, adIndexInAdGroup); } @Override public void handlePrepareError( AdsMediaSource adsMediaSource, int adGroupIndex, int adIndexInAdGroup, IOException exception) { if (player == null) { return; } checkNotNull(adTagLoaderByAdsMediaSource.get(adsMediaSource)) .handlePrepareError(adGroupIndex, adIndexInAdGroup, exception); } // Internal methods. private void maybeUpdateCurrentAdTagLoader() { @Nullable AdTagLoader [MASK] = currentAdTagLoader; @Nullable AdTagLoader newAdTagLoader = getCurrentAdTagLoader(); if (!Util.areEqual( [MASK] , newAdTagLoader)) { if ( [MASK] != null) { [MASK] .deactivate(); } currentAdTagLoader = newAdTagLoader; if (newAdTagLoader != null) { newAdTagLoader.activate(checkNotNull(player)); } } } @Nullable private AdTagLoader getCurrentAdTagLoader() { @Nullable Player player = this.player; if (player == null) { return null; } Timeline timeline = player.getCurrentTimeline(); if (timeline.isEmpty()) { return null; } int periodIndex = player.getCurrentPeriodIndex(); @Nullable Object adsId = timeline.getPeriod(periodIndex, period).getAdsId(); if (adsId == null) { return null; } @Nullable AdTagLoader adTagLoader = adTagLoaderByAdsId.get(adsId); if (adTagLoader == null || !adTagLoaderByAdsMediaSource.containsValue(adTagLoader)) { return null; } return adTagLoader; } private void maybePreloadNextPeriodAds() { @Nullable Player player = ImaAdsLoader.this.player; if (player == null) { return; } Timeline timeline = player.getCurrentTimeline(); if (timeline.isEmpty()) { return; } int nextPeriodIndex = timeline.getNextPeriodIndex( player.getCurrentPeriodIndex(), period, window, player.getRepeatMode(), player.getShuffleModeEnabled()); if (nextPeriodIndex == C.INDEX_UNSET) { return; } timeline.getPeriod(nextPeriodIndex, period); @Nullable Object nextAdsId = period.getAdsId(); if (nextAdsId == null) { return; } @Nullable AdTagLoader nextAdTagLoader = adTagLoaderByAdsId.get(nextAdsId); if (nextAdTagLoader == null || nextAdTagLoader == currentAdTagLoader) { return; } long periodPositionUs = timeline.getPeriodPositionUs( window, period, period.windowIndex, /* windowPositionUs= */ C.TIME_UNSET) .second; nextAdTagLoader.maybePreloadAds(Util.usToMs(periodPositionUs), Util.usToMs(period.durationUs)); } private final class PlayerListenerImpl implements Player.Listener { @Override public void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) { if (timeline.isEmpty()) { // The player is being reset or contains no media. return; } maybeUpdateCurrentAdTagLoader(); maybePreloadNextPeriodAds(); } @Override public void onPositionDiscontinuity( Player.PositionInfo oldPosition, Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) { maybeUpdateCurrentAdTagLoader(); maybePreloadNextPeriodAds(); } @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { maybePreloadNextPeriodAds(); } @Override public void onRepeatModeChanged(@Player.RepeatMode int repeatMode) { maybePreloadNextPeriodAds(); } } /** * Default {@link ImaUtil.ImaFactory} for non-test usage, which delegates to {@link * ImaSdkFactory}. */ private static final class DefaultImaFactory implements ImaUtil.ImaFactory { @Override public ImaSdkSettings createImaSdkSettings() { ImaSdkSettings settings = ImaSdkFactory.getInstance().createImaSdkSettings(); settings.setLanguage(Util.getSystemLanguageCodes()[0]); return settings; } @Override public AdsRenderingSettings createAdsRenderingSettings() { return ImaSdkFactory.getInstance().createAdsRenderingSettings(); } @Override public AdDisplayContainer createAdDisplayContainer(ViewGroup container, VideoAdPlayer player) { return ImaSdkFactory.createAdDisplayContainer(container, player); } @Override public AdDisplayContainer createAudioAdDisplayContainer(Context context, VideoAdPlayer player) { return ImaSdkFactory.createAudioAdDisplayContainer(context, player); } // The reasonDetail parameter to createFriendlyObstruction is annotated @Nullable but the // annotation is not kept in the obfuscated dependency. @SuppressWarnings(""nullness:argument"") @Override public FriendlyObstruction createFriendlyObstruction( View view, FriendlyObstructionPurpose friendlyObstructionPurpose, @Nullable String reasonDetail) { return ImaSdkFactory.getInstance() .createFriendlyObstruction(view, friendlyObstructionPurpose, reasonDetail); } @Override public AdsRequest createAdsRequest() { return ImaSdkFactory.getInstance().createAdsRequest(); } @Override public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) { return ImaSdkFactory.getInstance() .createAdsLoader(context, imaSdkSettings, adDisplayContainer); } } } ","oldAdTagLoader " "// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.packages; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.devtools.build.lib.analysis.testing.ExecGroupSubject.assertThat; import static com.google.devtools.build.lib.analysis.testing.RuleClassSubject.assertThat; import static com.google.devtools.build.lib.packages.Attribute.attr; import static com.google.devtools.build.lib.packages.BuildType.LABEL; import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL; import static com.google.devtools.build.lib.packages.BuildType.OUTPUT_LIST; import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.substitutePlaceholderIntoTemplate; import static com.google.devtools.build.lib.packages.RuleClass.Builder.STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME; import static com.google.devtools.build.lib.packages.RuleClass.NO_EXTERNAL_BINDINGS; import static com.google.devtools.build.lib.packages.RuleClass.NO_TOOLCHAINS_TO_REGISTER; import static com.google.devtools.build.lib.packages.Type.BOOLEAN; import static com.google.devtools.build.lib.packages.Type.INTEGER; import static com.google.devtools.build.lib.packages.Type.STRING; import static com.google.devtools.build.lib.packages.Type.STRING_LIST; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.devtools.build.lib.analysis.config.Fragment; import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement; import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.RepositoryMapping; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventCollector; import com.google.devtools.build.lib.events.EventKind; import com.google.devtools.build.lib.packages.Attribute.StarlarkComputedDefaultTemplate.CannotPrecomputeDefaultsException; import com.google.devtools.build.lib.packages.Attribute.ValidityPredicate; import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory; import com.google.devtools.build.lib.packages.RuleClass.ToolchainResolutionMode; import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap; import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import net.starlark.java.eval.StarlarkFunction; import net.starlark.java.eval.StarlarkInt; import net.starlark.java.eval.StarlarkSemantics; import net.starlark.java.eval.StarlarkThread; import net.starlark.java.syntax.Location; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link RuleClass}. */ @RunWith(JUnit4.class) public final class RuleClassTest extends PackageLoadingTestCase { private static final RuleClass.ConfiguredTargetFactory DUMMY_CONFIGURED_TARGET_FACTORY = ruleContext -> { throw new IllegalStateException(); }; private static final ImmutableList DUMMY_STACK = ImmutableList.of( StarlarkThread.callStackEntry( StarlarkThread.TOP_LEVEL, Location.fromFileLineColumn(""BUILD"", 10, 1)), StarlarkThread.callStackEntry(""bar"", Location.fromFileLineColumn(""bar.bzl"", 42, 1)), StarlarkThread.callStackEntry(""rule"", Location.BUILTIN)); private static final class DummyFragment extends Fragment {} private static RuleClass createRuleClassA() throws LabelSyntaxException { return newRuleClass( ""ruleA"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""my-string-attr"", STRING).mandatory().build(), attr(""my-label-attr"", LABEL) .mandatory() .legacyAllowAnyFileType() .value(Label.parseCanonical(""//default:label"")) .build(), attr(""my-labellist-attr"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(), attr(""my-integer-attr"", INTEGER).value(StarlarkInt.of(42)).build(), attr(""my-string-attr2"", STRING).mandatory().value((String) null).build(), attr(""my-stringlist-attr"", STRING_LIST).build(), attr(""my-sorted-stringlist-attr"", STRING_LIST).orderIndependent().build()); } private static RuleClass createRuleClassB(RuleClass ruleClassA) { // emulates attribute inheritance List attributes = new ArrayList<>(ruleClassA.getAttributes()); attributes.add(attr(""another-string-attr"", STRING).mandatory().build()); return newRuleClass( ""ruleB"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attributes.toArray(new Attribute[0])); } @Test public void testRuleClassBasics() throws Exception { RuleClass ruleClassA = createRuleClassA(); assertThat(ruleClassA.getName()).isEqualTo(""ruleA""); assertThat(ruleClassA.getAttributeCount()).isEqualTo(8); assertThat(ruleClassA.getAttributeIndex(""name"")).isEqualTo(0); assertThat(ruleClassA.getAttributeIndex(""my-string-attr"")).isEqualTo(1); assertThat(ruleClassA.getAttributeIndex(""my-label-attr"")).isEqualTo(2); assertThat(ruleClassA.getAttributeIndex(""my-labellist-attr"")).isEqualTo(3); assertThat(ruleClassA.getAttributeIndex(""my-integer-attr"")).isEqualTo(4); assertThat(ruleClassA.getAttributeIndex(""my-string-attr2"")).isEqualTo(5); assertThat(ruleClassA.getAttributeIndex(""my-stringlist-attr"")).isEqualTo(6); assertThat(ruleClassA.getAttributeIndex(""my-sorted-stringlist-attr"")).isEqualTo(7); assertThat(ruleClassA.getAttributeByName(""name"")).isEqualTo(ruleClassA.getAttribute(0)); assertThat(ruleClassA.getAttributeByName(""my-string-attr"")) .isEqualTo(ruleClassA.getAttribute(1)); assertThat(ruleClassA.getAttributeByName(""my-label-attr"")) .isEqualTo(ruleClassA.getAttribute(2)); assertThat(ruleClassA.getAttributeByName(""my-labellist-attr"")) .isEqualTo(ruleClassA.getAttribute(3)); assertThat(ruleClassA.getAttributeByName(""my-integer-attr"")) .isEqualTo(ruleClassA.getAttribute(4)); assertThat(ruleClassA.getAttributeByName(""my-string-attr2"")) .isEqualTo(ruleClassA.getAttribute(5)); assertThat(ruleClassA.getAttributeByName(""my-stringlist-attr"")) .isEqualTo(ruleClassA.getAttribute(6)); assertThat(ruleClassA.getAttributeByName(""my-sorted-stringlist-attr"")) .isEqualTo(ruleClassA.getAttribute(7)); // default based on type assertThat(ruleClassA.getAttribute(0).getDefaultValue(null)).isEqualTo(""""); assertThat(ruleClassA.getAttribute(1).getDefaultValue(null)).isEqualTo(""""); assertThat(ruleClassA.getAttribute(2).getDefaultValue(null)) .isEqualTo(Label.parseCanonical(""//default:label"")); assertThat(ruleClassA.getAttribute(3).getDefaultValue(null)).isEqualTo(ImmutableList.of()); assertThat(ruleClassA.getAttribute(4).getDefaultValue(null)).isEqualTo(StarlarkInt.of(42)); // default explicitly specified assertThat(ruleClassA.getAttribute(5).getDefaultValue(null)).isNull(); assertThat(ruleClassA.getAttribute(6).getDefaultValue(null)).isEqualTo(ImmutableList.of()); assertThat(ruleClassA.getAttribute(7).getDefaultValue(null)).isEqualTo(ImmutableList.of()); } @Test public void testRuleClassInheritance() throws Exception { RuleClass ruleClassA = createRuleClassA(); RuleClass ruleClassB = createRuleClassB(ruleClassA); assertThat(ruleClassB.getName()).isEqualTo(""ruleB""); assertThat(ruleClassB.getAttributeCount()).isEqualTo(9); assertThat(ruleClassB.getAttributeIndex(""name"")).isEqualTo(0); assertThat(ruleClassB.getAttributeIndex(""my-string-attr"")).isEqualTo(1); assertThat(ruleClassB.getAttributeIndex(""my-label-attr"")).isEqualTo(2); assertThat(ruleClassB.getAttributeIndex(""my-labellist-attr"")).isEqualTo(3); assertThat(ruleClassB.getAttributeIndex(""my-integer-attr"")).isEqualTo(4); assertThat(ruleClassB.getAttributeIndex(""my-string-attr2"")).isEqualTo(5); assertThat(ruleClassB.getAttributeIndex(""my-stringlist-attr"")).isEqualTo(6); assertThat(ruleClassB.getAttributeIndex(""my-sorted-stringlist-attr"")).isEqualTo(7); assertThat(ruleClassB.getAttributeIndex(""another-string-attr"")).isEqualTo(8); assertThat(ruleClassB.getAttributeByName(""name"")).isEqualTo(ruleClassB.getAttribute(0)); assertThat(ruleClassB.getAttributeByName(""my-string-attr"")) .isEqualTo(ruleClassB.getAttribute(1)); assertThat(ruleClassB.getAttributeByName(""my-label-attr"")) .isEqualTo(ruleClassB.getAttribute(2)); assertThat(ruleClassB.getAttributeByName(""my-labellist-attr"")) .isEqualTo(ruleClassB.getAttribute(3)); assertThat(ruleClassB.getAttributeByName(""my-integer-attr"")) .isEqualTo(ruleClassB.getAttribute(4)); assertThat(ruleClassB.getAttributeByName(""my-string-attr2"")) .isEqualTo(ruleClassB.getAttribute(5)); assertThat(ruleClassB.getAttributeByName(""my-stringlist-attr"")) .isEqualTo(ruleClassB.getAttribute(6)); assertThat(ruleClassB.getAttributeByName(""my-sorted-stringlist-attr"")) .isEqualTo(ruleClassB.getAttribute(7)); assertThat(ruleClassB.getAttributeByName(""another-string-attr"")) .isEqualTo(ruleClassB.getAttribute(8)); } private static final String TEST_PACKAGE_NAME = ""testpackage""; private static final String TEST_RULE_NAME = ""my-rule-A""; private static final int TEST_RULE_DEFINED_AT_LINE = 42; private static final String TEST_RULE_LABEL = ""@//"" + TEST_PACKAGE_NAME + "":"" + TEST_RULE_NAME; private Path testBuildfilePath; private Location testRuleLocation; @Before public void setRuleLocation() { testBuildfilePath = root.getRelative(""testpackage/BUILD""); testRuleLocation = Location.fromFileLineColumn(testBuildfilePath.toString(), TEST_RULE_DEFINED_AT_LINE, 0); } private Package.Builder createDummyPackageBuilder() { return packageFactory .newPackageBuilder( PackageIdentifier.createInMainRepo(TEST_PACKAGE_NAME), ""TESTING"", Optional.empty(), Optional.empty(), StarlarkSemantics.DEFAULT, RepositoryMapping.ALWAYS_FALLBACK, RepositoryMapping.ALWAYS_FALLBACK) .setFilename(RootedPath.toRootedPath(root, testBuildfilePath)); } @Test public void testDuplicatedDeps() throws Exception { RuleClass depsRuleClass = newRuleClass( ""ruleDeps"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""list1"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(), attr(""list2"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(), attr(""list3"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); // LinkedHashMap -> predictable iteration order for testing Map attributeValues = new LinkedHashMap<>(); attributeValues.put(""list1"", Lists.newArrayList(""//testpackage:dup1"", "":dup1"", "":nodup"")); attributeValues.put(""list2"", Lists.newArrayList("":nodup1"", "":nodup2"")); attributeValues.put(""list3"", Lists.newArrayList("":dup1"", "":dup1"", "":dup2"", "":dup2"")); reporter.removeHandler(failFastHandler); createRule(depsRuleClass, ""depsRule"", attributeValues); assertThat(eventCollector.count()).isSameInstanceAs(3); assertDupError(""//testpackage:dup1"", ""list1"", ""depsRule""); assertDupError(""//testpackage:dup1"", ""list3"", ""depsRule""); assertDupError(""//testpackage:dup2"", ""list3"", ""depsRule""); } private void assertDupError(String label, String attrName, String ruleName) { assertContainsEvent(String.format(""Label '%s' is duplicated in the '%s' attribute of rule '%s'"", label, attrName, ruleName)); } @Test public void testDuplicatedDepsWithinSingleSelectConditionError() throws Exception { RuleClass depsRuleClass = newRuleClass( ""ruleDeps"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""list1"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); SelectorList selectorList1 = SelectorList.of( new SelectorValue( ImmutableMap.of(""//conditions:a"", ImmutableList.of("":dup1"", "":dup1"")), """")); // expect errors reporter.removeHandler(failFastHandler); Map attributeValues = new HashMap<>(); attributeValues.put(""list1"", selectorList1); createRule(depsRuleClass, ""depsRule"", attributeValues); assertThat(eventCollector.count()).isSameInstanceAs(1); assertDupError(""//testpackage:dup1"", ""list1"", ""depsRule""); } @Test public void testDuplicatedDepsWithinConditionMultipleSelectsErrors() throws Exception { RuleClass depsRuleClass = newRuleClass( ""ruleDeps"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""list1"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); SelectorList selectorList1a = SelectorList.of( new SelectorValue( ImmutableMap.of( ""//conditions:a"", ImmutableList.of("":dup1"", ""dup1""), ""//conditions:b"", ImmutableList.of("":nodup1"")), """")); SelectorList selectorList1b = SelectorList.of( new SelectorValue( ImmutableMap.of( ""//conditions:c"", ImmutableList.of("":dup2"", ""dup2""), ""//conditions:d"", ImmutableList.of("":nodup1"")), """")); SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b); // expect errors reporter.removeHandler(failFastHandler); Map attributeValues = new HashMap<>(); attributeValues.put(""list1"", selectorList1); createRule(depsRuleClass, ""depsRule"", attributeValues); assertThat(eventCollector.count()).isSameInstanceAs(2); assertDupError(""//testpackage:dup1"", ""list1"", ""depsRule""); assertDupError(""//testpackage:dup2"", ""list1"", ""depsRule""); } @Test public void testSameDepAcrossMultipleSelectsNoDuplicateNoError() throws Exception { RuleClass depsRuleClass = newRuleClass( ""ruleDeps"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""list1"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); // ignore duplicatess across selects where values appear duplicated but are not SelectorList selectorList1a = SelectorList.of( new SelectorValue( ImmutableMap.of( ""//conditions:a"", ImmutableList.of("":nodup1""), ""//conditions:b"", ImmutableList.of("":nodup2"")), """")); SelectorList selectorList1b = SelectorList.of( new SelectorValue( ImmutableMap.of( ""//conditions:a"", ImmutableList.of("":nodup2""), ""//conditions:b"", ImmutableList.of("":nodup1"")), """")); SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b); Map attributeValues = new HashMap<>(); attributeValues.put(""list1"", selectorList1); createRule(depsRuleClass, ""depsRule"", attributeValues); } @Test public void testSameDepAcrossMultipleSelectsIsDuplicateNoError() throws Exception { RuleClass depsRuleClass = newRuleClass( ""ruleDeps"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""list1"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); // repetition of dup1 is identified at analysis time, not loading time SelectorList selectorList1a = SelectorList.of( new SelectorValue( ImmutableMap.of( ""//conditions:a"", ImmutableList.of("":dup1""), ""//conditions:b"", ImmutableList.of("":nodup1"")), """")); SelectorList selectorList1b = SelectorList.of( new SelectorValue( ImmutableMap.of( ""//conditions:a"", ImmutableList.of("":dup1""), ""//conditions:b"", ImmutableList.of("":nodup2"")), """")); SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b); Map attributeValues = new HashMap<>(); attributeValues.put(""list1"", selectorList1); createRule(depsRuleClass, ""depsRule"", attributeValues); } @Test public void testSameDepAcrossConditionsInSelectNoError() throws Exception { RuleClass depsRuleClass = newRuleClass( ""ruleDeps"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""list1"", LABEL_LIST).mandatory().legacyAllowAnyFileType().build()); SelectorList selectorList1 = SelectorList.of( new SelectorValue( ImmutableMap.of( ""//conditions:a"", ImmutableList.of("":nodup1""), ""//conditions:b"", ImmutableList.of("":nodup1"")), """")); Map attributeValues = new HashMap<>(); attributeValues.put(""list1"", selectorList1); createRule(depsRuleClass, ""depsRule"", attributeValues); } @Test public void testCreateRule() throws Exception { RuleClass ruleClassA = createRuleClassA(); // LinkedHashMap -> predictable iteration order for testing Map attributeValues = new LinkedHashMap<>(); attributeValues.put(""my-labellist-attr"", ""foobar""); // wrong type attributeValues.put(""bogus-attr"", ""foobar""); // no such attr attributeValues.put(""my-stringlist-attr"", Arrays.asList(""foo"", ""bar"")); reporter.removeHandler(failFastHandler); EventCollector collector = new EventCollector(EventKind.ERRORS); reporter.addHandler(collector); Rule rule = createRule(ruleClassA, TEST_RULE_NAME, attributeValues); // TODO(blaze-team): (2009) refactor to use assertContainsEvent Iterator expectedMessages = Arrays.asList( ""expected value of type 'list(label)' for attribute 'my-labellist-attr' "" + ""in 'ruleA' rule, but got \""foobar\"" (string)"", ""no such attribute 'bogus-attr' in 'ruleA' rule"", ""missing value for mandatory "" + ""attribute 'my-string-attr' in 'ruleA' rule"", ""missing value for mandatory attribute 'my-label-attr' in 'ruleA' rule"", ""missing value for mandatory "" + ""attribute 'my-labellist-attr' in 'ruleA' rule"", ""missing value for mandatory "" + ""attribute 'my-string-attr2' in 'ruleA' rule"" ).iterator(); for (Event event : collector) { assertThat(event.getLocation().line()).isEqualTo(TEST_RULE_DEFINED_AT_LINE); assertThat(event.getLocation().file()).isEqualTo(testBuildfilePath.toString()); assertThat(event.getMessage()) .isEqualTo(TEST_RULE_LABEL.substring(1) + "": "" + expectedMessages.next()); } // Test basic rule properties: assertThat(rule.getRuleClass()).isEqualTo(""ruleA""); assertThat(rule.getName()).isEqualTo(TEST_RULE_NAME); assertThat(rule.getLabel().toString()).isEqualTo(TEST_RULE_LABEL.substring(1)); // Test attribute access: AttributeMap attributes = RawAttributeMapper.of(rule); assertThat(attributes.get(""my-label-attr"", BuildType.LABEL).toString()) .isEqualTo(""//default:label""); assertThat(attributes.get(""my-integer-attr"", Type.INTEGER).toIntUnchecked()).isEqualTo(42); // missing attribute -> default chosen based on type assertThat(attributes.get(""my-string-attr"", Type.STRING)).isEmpty(); assertThat(attributes.get(""my-labellist-attr"", BuildType.LABEL_LIST)).isEmpty(); assertThat(attributes.get(""my-stringlist-attr"", Type.STRING_LIST)) .isEqualTo(Arrays.asList(""foo"", ""bar"")); IllegalArgumentException e = assertThrows( IllegalArgumentException.class, () -> attributes.get(""my-labellist-attr"", Type.STRING)); assertThat(e) .hasMessageThat() .isEqualTo( ""Attribute my-labellist-attr is of type list(label) "" + ""and not of type string in ruleA rule //testpackage:my-rule-A""); } @Test public void testImplicitOutputs() throws Exception { RuleClass ruleClassC = newRuleClass( ""ruleC"", false, false, false, false, false, false, ImplicitOutputsFunction.fromTemplates( ""foo-%{name}.bar"", ""lib%{name}-wazoo-%{name}.mumble"", ""stuff-%{outs}-bar""), null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""outs"", OUTPUT_LIST).build()); Map attributeValues = new HashMap<>(); attributeValues.put(""outs"", Collections.singletonList(""explicit_out"")); attributeValues.put(""name"", ""myrule""); Rule rule = createRule(ruleClassC, ""myrule"", attributeValues); Set set = new HashSet<>(); for (OutputFile outputFile : rule.getOutputFiles()) { set.add(outputFile.getName()); assertThat(outputFile.getGeneratingRule()).isSameInstanceAs(rule); } assertThat(set).containsExactly(""foo-myrule.bar"", ""libmyrule-wazoo-myrule.mumble"", ""stuff-explicit_out-bar"", ""explicit_out""); } @Test public void testImplicitOutsWithBasenameDirname() throws Exception { RuleClass ruleClass = newRuleClass( ""ruleClass"", false, false, false, false, false, false, ImplicitOutputsFunction.fromTemplates(""%{dirname}lib%{basename}.bar""), null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true); Rule rule = createRule(ruleClass, ""myRule"", ImmutableMap.of()); assertThat(Iterables.getOnlyElement(rule.getOutputFiles()).getName()) .isEqualTo(""libmyRule.bar""); Rule ruleWithSlash = createRule(ruleClass, ""myRule/with/slash"", ImmutableMap.of()); assertThat(Iterables.getOnlyElement(ruleWithSlash.getOutputFiles()).getName()) .isEqualTo(""myRule/with/libslash.bar""); } /** * Helper routine that instantiates a rule class with the given computed default and supporting * attributes for the default to reference. */ private static RuleClass getRuleClassWithComputedDefault(Attribute computedDefault) { return newRuleClass( ""ruleClass"", false, false, false, false, false, false, ImplicitOutputsFunction.fromTemplates(""empty""), null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""condition"", BOOLEAN).value(false).build(), attr(""declared1"", BOOLEAN).value(false).build(), attr(""declared2"", BOOLEAN).value(false).build(), attr(""nonconfigurable"", BOOLEAN).nonconfigurable(""test"").value(false).build(), computedDefault); } /** * Helper routine that checks that a computed default is valid and bound to the expected value. */ private void checkValidComputedDefault(Object expectedValue, Attribute computedDefault, ImmutableMap attrValueMap) throws Exception { assertThat(computedDefault.getDefaultValueUnchecked()) .isInstanceOf(Attribute.ComputedDefault.class); Rule rule = createRule(getRuleClassWithComputedDefault(computedDefault), ""myRule"", attrValueMap); AttributeMap attributes = RawAttributeMapper.of(rule); assertThat(attributes.get(computedDefault.getName(), computedDefault.getType())) .isEqualTo(expectedValue); } /** * Helper routine that checks that a computed default is invalid due to declared dependency issues * and fails with the expected message. */ private void checkInvalidComputedDefault(Attribute computedDefault, String expectedMessage) { IllegalArgumentException e = assertThrows( IllegalArgumentException.class, () -> createRule( getRuleClassWithComputedDefault(computedDefault), ""myRule"", ImmutableMap.of())); assertThat(e).hasMessageThat().isEqualTo(expectedMessage); } /** Tests computed default values are computed as expected. */ @Test public void testComputedDefault() throws Exception { Attribute computedDefault = attr(""$result"", BOOLEAN) .value( new Attribute.ComputedDefault(""condition"") { @Override public Object getDefault(AttributeMap rule) { return rule.get(""condition"", Type.BOOLEAN); } }) .build(); checkValidComputedDefault( Boolean.FALSE, computedDefault, ImmutableMap.of(""condition"", Boolean.FALSE)); checkValidComputedDefault( Boolean.TRUE, computedDefault, ImmutableMap.of(""condition"", Boolean.TRUE)); } /** * Tests that computed defaults can only read attribute values for configurable attributes that * have been explicitly declared. */ @Test public void testComputedDefaultDeclarations() throws Exception { checkValidComputedDefault( Boolean.FALSE, attr(""$good_default_no_declares"", BOOLEAN) .value( new Attribute.ComputedDefault() { @Override public Object getDefault(AttributeMap rule) { // OK: not a value check: return rule.isAttributeValueExplicitlySpecified(""undeclared""); } }) .build(), ImmutableMap.of()); checkValidComputedDefault( Boolean.FALSE, attr(""$good_default_one_declare"", BOOLEAN) .value( new Attribute.ComputedDefault(""declared1"") { @Override public Object getDefault(AttributeMap rule) { return rule.get(""declared1"", Type.BOOLEAN); } }) .build(), ImmutableMap.of()); checkValidComputedDefault( Boolean.FALSE, attr(""$good_default_two_declares"", BOOLEAN) .value( new Attribute.ComputedDefault(""declared1"", ""declared2"") { @Override public Object getDefault(AttributeMap rule) { return rule.get(""declared1"", Type.BOOLEAN) && rule.get(""declared2"", Type.BOOLEAN); } }) .build(), ImmutableMap.of()); checkInvalidComputedDefault( attr(""$bad_default_no_declares"", BOOLEAN).value( new Attribute.ComputedDefault() { @Override public Object getDefault(AttributeMap rule) { return rule.get(""declared1"", Type.BOOLEAN); } }).build(), ""attribute \""declared1\"" isn't available in this computed default context""); checkInvalidComputedDefault( attr(""$bad_default_one_declare"", BOOLEAN).value( new Attribute.ComputedDefault(""declared1"") { @Override public Object getDefault(AttributeMap rule) { return rule.get(""declared1"", Type.BOOLEAN) || rule.get(""declared2"", Type.BOOLEAN); } }).build(), ""attribute \""declared2\"" isn't available in this computed default context""); checkInvalidComputedDefault( attr(""$bad_default_two_declares"", BOOLEAN).value( new Attribute.ComputedDefault(""declared1"", ""declared2"") { @Override public Object getDefault(AttributeMap rule) { return rule.get(""condition"", Type.BOOLEAN); } }).build(), ""attribute \""condition\"" isn't available in this computed default context""); } /** * Tests that computed defaults *can* read attribute values for non-configurable attributes * without needing to explicitly declare them. */ @Test public void testComputedDefaultWithNonConfigurableAttributes() throws Exception { checkValidComputedDefault( Boolean.FALSE, attr(""$good_default_reading_undeclared_nonconfigurable_attribute"", BOOLEAN) .value( new Attribute.ComputedDefault() { @Override public Object getDefault(AttributeMap rule) { return rule.get(""nonconfigurable"", Type.BOOLEAN); } }) .build(), ImmutableMap.of()); } @Test public void testOutputsAreOrdered() throws Exception { RuleClass ruleClassC = newRuleClass( ""ruleC"", false, false, false, false, false, false, ImplicitOutputsFunction.fromTemplates(""first-%{name}"", ""second-%{name}"", ""out-%{outs}""), null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""outs"", OUTPUT_LIST).build()); Map attributeValues = new HashMap<>(); attributeValues.put(""outs"", ImmutableList.of(""third"", ""fourth"")); attributeValues.put(""name"", ""myrule""); Rule rule = createRule(ruleClassC, ""myrule"", attributeValues); List actual = new ArrayList<>(); for (OutputFile outputFile : rule.getOutputFiles()) { actual.add(outputFile.getName()); assertThat(outputFile.getGeneratingRule()).isSameInstanceAs(rule); } assertWithMessage(""unexpected output set"").that(actual).containsExactly(""first-myrule"", ""second-myrule"", ""out-third"", ""out-fourth"", ""third"", ""fourth""); assertWithMessage(""invalid output ordering"").that(actual).containsExactly(""first-myrule"", ""second-myrule"", ""out-third"", ""out-fourth"", ""third"", ""fourth"").inOrder(); } @Test public void testSubstitutePlaceholderIntoTemplate() throws Exception { RuleClass ruleClass = newRuleClass( ""ruleA"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(), true, attr(""a"", STRING_LIST).mandatory().build(), attr(""b"", STRING_LIST).mandatory().build(), attr(""c"", STRING_LIST).mandatory().build(), attr(""baz"", STRING_LIST).mandatory().build(), attr(""empty"", STRING_LIST).build()); Map attributeValues = new LinkedHashMap<>(); attributeValues.put(""a"", ImmutableList.of(""a"", ""A"")); attributeValues.put(""b"", ImmutableList.of(""b"", ""B"")); attributeValues.put(""c"", ImmutableList.of(""c"", ""C"")); attributeValues.put(""baz"", ImmutableList.of(""baz"", ""BAZ"")); attributeValues.put(""empty"", ImmutableList.of()); AttributeMap rule = RawAttributeMapper.of(createRule(ruleClass, ""testrule"", attributeValues)); assertThat(substitutePlaceholderIntoTemplate(""foo"", rule)).containsExactly(""foo""); assertThat(substitutePlaceholderIntoTemplate(""foo-%{baz}-bar"", rule)).containsExactly( ""foo-baz-bar"", ""foo-BAZ-bar"").inOrder(); assertThat(substitutePlaceholderIntoTemplate(""%{a}-%{b}-%{c}"", rule)).containsExactly(""a-b-c"", ""a-b-C"", ""a-B-c"", ""a-B-C"", ""A-b-c"", ""A-b-C"", ""A-B-c"", ""A-B-C"").inOrder(); assertThat(substitutePlaceholderIntoTemplate(""%{a"", rule)).containsExactly(""%{a""); assertThat(substitutePlaceholderIntoTemplate(""%{a}}"", rule)).containsExactly(""a}"", ""A}"") .inOrder(); assertThat(substitutePlaceholderIntoTemplate(""x%{a}y%{empty}"", rule)).isEmpty(); } @Test public void testOrderIndependentAttribute() throws Exception { RuleClass ruleClassA = createRuleClassA(); List list = Arrays.asList(""foo"", ""bar"", ""baz""); Map attributeValues = new LinkedHashMap<>(); // mandatory values attributeValues.put(""my-string-attr"", """"); attributeValues.put(""my-label-attr"", ""//project""); attributeValues.put(""my-string-attr2"", """"); attributeValues.put(""my-labellist-attr"", Collections.emptyList()); // to compare the effect of .orderIndependent() attributeValues.put(""my-stringlist-attr"", list); attributeValues.put(""my-sorted-stringlist-attr"", list); Rule rule = createRule(ruleClassA, ""testrule"", attributeValues); AttributeMap attributes = RawAttributeMapper.of(rule); assertThat(attributes.get(""my-stringlist-attr"", Type.STRING_LIST)).isEqualTo(list); assertThat(attributes.get(""my-sorted-stringlist-attr"", Type.STRING_LIST)) .isEqualTo(Arrays.asList(""bar"", ""baz"", ""foo"")); } @CanIgnoreReturnValue private Rule createRule(RuleClass ruleClass, String name, Map attributeValues) throws LabelSyntaxException, InterruptedException, CannotPrecomputeDefaultsException { Package.Builder pkgBuilder = createDummyPackageBuilder(); Label ruleLabel; try { ruleLabel = pkgBuilder.createLabel(name); } catch (LabelSyntaxException e) { throw new IllegalArgumentException(""Rule has illegal label"", e); } return ruleClass.createRule( pkgBuilder, ruleLabel, new BuildLangTypedAttributeValuesMap(attributeValues), true, reporter, ImmutableList.of( StarlarkThread.callStackEntry(StarlarkThread.TOP_LEVEL, testRuleLocation))); } @Test public void testOverrideWithWrongType() { RuleClass parentRuleClass = createParentRuleClass(); RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder(""child_rule"", RuleClassType.NORMAL, false, parentRuleClass); IllegalStateException e = assertThrows( IllegalStateException.class, () -> childRuleClassBuilder.override(attr(""attr"", INTEGER))); assertThat(e) .hasMessageThat() .isEqualTo( ""The type of the new attribute 'int' is different from "" + ""the original one 'string'.""); } @Test public void testOverrideWithRightType() { RuleClass parentRuleClass = createParentRuleClass(); RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder( ""child_rule"", RuleClassType.NORMAL, false, parentRuleClass); childRuleClassBuilder.override(attr(""attr"", STRING)); } @Test public void testCopyAndOverrideAttribute() throws Exception { RuleClass parentRuleClass = createParentRuleClass(); RuleClass childRuleClass = createChildRuleClass(parentRuleClass); Map parentValues = new LinkedHashMap<>(); Map childValues = new LinkedHashMap<>(); childValues.put(""attr"", ""somevalue""); createRule(parentRuleClass, ""parent_rule"", parentValues); createRule(childRuleClass, ""child_rule"", childValues); } @Test public void testCopyAndOverrideAttributeMandatoryMissing() throws Exception { RuleClass parentRuleClass = createParentRuleClass(); RuleClass childRuleClass = createChildRuleClass(parentRuleClass); Map childValues = new LinkedHashMap<>(); reporter.removeHandler(failFastHandler); createRule(childRuleClass, ""child_rule"", childValues); assertThat(eventCollector.count()).isSameInstanceAs(1); assertContainsEvent(""//testpackage:child_rule: missing value for mandatory "" + ""attribute 'attr' in 'child_rule' rule""); } @Test public void testRequiredFragmentInheritance() { RuleClass parentRuleClass = createParentRuleClass(); RuleClass childRuleClass = createChildRuleClass(parentRuleClass); assertThat(parentRuleClass.getConfigurationFragmentPolicy().getRequiredConfigurationFragments()) .containsExactly(DummyFragment.class); assertThat(childRuleClass.getConfigurationFragmentPolicy().getRequiredConfigurationFragments()) .containsExactly(DummyFragment.class); } private static RuleClass newRuleClass( String name, boolean starlarkExecutable, boolean documented, boolean binaryOutput, boolean workspaceOnly, boolean outputsDefaultExecutable, boolean isAnalysisTest, ImplicitOutputsFunction [MASK] , TransitionFactory transitionFactory, ConfiguredTargetFactory configuredTargetFactory, PredicateWithMessage validityPredicate, AdvertisedProviderSet advertisedProviders, @Nullable StarlarkFunction configuredTargetFunction, Set> allowedConfigurationFragments, boolean supportsConstraintChecking, Attribute... attributes) { return new RuleClass( name, DUMMY_STACK, /* key= */ name, RuleClassType.NORMAL, /* isStarlark= */ starlarkExecutable, /* starlarkTestable= */ false, documented, binaryOutput, workspaceOnly, outputsDefaultExecutable, isAnalysisTest, /* hasAnalysisTestTransition= */ false, /* allowlistCheckers= */ ImmutableList.of(), /* ignoreLicenses= */ false, [MASK] , transitionFactory, configuredTargetFactory, validityPredicate, advertisedProviders, configuredTargetFunction, NO_EXTERNAL_BINDINGS, NO_TOOLCHAINS_TO_REGISTER, /* optionReferenceFunction= */ RuleClass.NO_OPTION_REFERENCE, /* ruleDefinitionEnvironmentLabel= */ null, /* ruleDefinitionEnvironmentDigest= */ null, new ConfigurationFragmentPolicy.Builder() .requiresConfigurationFragments(allowedConfigurationFragments) .build(), supportsConstraintChecking, /* toolchainTypes= */ ImmutableSet.of(), /* useToolchainResolution= */ ToolchainResolutionMode.ENABLED, /* executionPlatformConstraints= */ ImmutableSet.of(), /* execGroups= */ ImmutableMap.of(), OutputFile.Kind.FILE, attributes.length > 0 && attributes[0].equals(RuleClass.NAME_ATTRIBUTE) ? ImmutableList.copyOf(attributes) : ImmutableList.builder() .add(RuleClass.NAME_ATTRIBUTE) .add(attributes) .build(), /* buildSetting= */ null, /* subrules= */ ImmutableList.of()); } private static RuleClass createParentRuleClass() { return newRuleClass( ""parent_rule"", false, false, false, false, false, false, ImplicitOutputsFunction.NONE, null, DUMMY_CONFIGURED_TARGET_FACTORY, PredicatesWithMessage.alwaysTrue(), AdvertisedProviderSet.EMPTY, null, ImmutableSet.of(DummyFragment.class), true, attr(""attr"", STRING).build()); } private static RuleClass createChildRuleClass(RuleClass parentRuleClass) { RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder( ""child_rule"", RuleClassType.NORMAL, false, parentRuleClass); return childRuleClassBuilder.override( childRuleClassBuilder .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .copy(""attr"").mandatory()) .add(attr(""tags"", STRING_LIST)) .build(); } @Test public void testValidityChecker() throws Exception { Rule dep1 = createRule( new RuleClass.Builder(""dep1class"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)) .build(), ""dep1"", ImmutableMap.of()); Rule dep2 = createRule( new RuleClass.Builder(""dep2class"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)) .build(), ""dep2"", ImmutableMap.of()); ValidityPredicate checker = new ValidityPredicate() { @Override public String checkValid(Rule from, String toRuleClass, Set toRuleTags) { assertThat(from.getName()).isEqualTo(""top""); switch (toRuleClass) { case ""dep1class"": return ""pear""; case ""dep2class"": return null; default: fail(""invalid dependency""); return null; } } }; RuleClass topClass = new RuleClass.Builder(""top"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)) .add(attr(""deps"", LABEL_LIST).legacyAllowAnyFileType() .validityPredicate(checker)) .build(); Rule topRule = createRule(topClass, ""top"", ImmutableMap.of()); assertThat( topClass .getAttributeByName(""deps"") .getValidityPredicate() .checkValid(topRule, dep1.getRuleClass(), dep1.getRuleTags())) .isEqualTo(""pear""); assertThat( topClass .getAttributeByName(""deps"") .getValidityPredicate() .checkValid(topRule, dep2.getRuleClass(), dep2.getRuleTags())) .isNull(); } @Test public void testBadRuleClassNames() { expectError(RuleClassType.NORMAL, ""8abc""); expectError(RuleClassType.NORMAL, ""!abc""); expectError(RuleClassType.NORMAL, ""a b""); } private static void expectError(RuleClassType type, String name) { assertThrows(IllegalArgumentException.class, () -> type.checkName(name)); } @Test public void testToolchainTypes() throws Exception { RuleClass.Builder ruleClassBuilder = new RuleClass.Builder(""ruleClass"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)); ruleClassBuilder.addToolchainTypes( ToolchainTypeRequirement.create(Label.parseCanonical(""//toolchain:tc1"")), ToolchainTypeRequirement.create(Label.parseCanonical(""//toolchain:tc2""))); RuleClass ruleClass = ruleClassBuilder.build(); assertThat(ruleClass).hasToolchainType(""//toolchain:tc1""); assertThat(ruleClass).hasToolchainType(""//toolchain:tc2""); } @Test public void testExecutionPlatformConstraints() throws Exception { RuleClass.Builder ruleClassBuilder = new RuleClass.Builder(""ruleClass"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)); ruleClassBuilder.addExecutionPlatformConstraints( Label.parseCanonical(""//constraints:cv1""), Label.parseCanonical(""//constraints:cv2"")); RuleClass ruleClass = ruleClassBuilder.build(); assertThat(ruleClass.getExecutionPlatformConstraints()) .containsExactly( Label.parseCanonical(""//constraints:cv1""), Label.parseCanonical(""//constraints:cv2"")); } @Test public void testExecutionPlatformConstraints_inheritConstraintsFromParent() throws Exception { RuleClass parentRuleClass = new RuleClass.Builder(""$parentRuleClass"", RuleClassType.ABSTRACT, false) .add(attr(""tags"", STRING_LIST)) .addExecutionPlatformConstraints( Label.parseCanonical(""//constraints:cv1""), Label.parseCanonical(""//constraints:cv2"")) .build(); RuleClass childRuleClass = new RuleClass.Builder(""childRuleClass"", RuleClassType.NORMAL, false, parentRuleClass) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .build(); assertThat(childRuleClass.getExecutionPlatformConstraints()) .containsExactly( Label.parseCanonical(""//constraints:cv1""), Label.parseCanonical(""//constraints:cv2"")); } @Test public void testExecutionPlatformConstraints_inheritAndAddConstraints() throws Exception { RuleClass parentRuleClass = new RuleClass.Builder(""$parentRuleClass"", RuleClassType.ABSTRACT, false) .add(attr(""tags"", STRING_LIST)) .build(); RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder(""childRuleClass"", RuleClassType.NORMAL, false, parentRuleClass) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .addExecutionPlatformConstraints( Label.parseCanonical(""//constraints:cv1""), Label.parseCanonical(""//constraints:cv2"")); RuleClass childRuleClass = childRuleClassBuilder.build(); assertThat(childRuleClass.getExecutionPlatformConstraints()) .containsExactly( Label.parseCanonical(""//constraints:cv1""), Label.parseCanonical(""//constraints:cv2"")); } @Test public void testExecGroups() { RuleClass.Builder ruleClassBuilder = new RuleClass.Builder(""ruleClass"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)); Label toolchain = Label.parseCanonicalUnchecked(""//toolchain""); Label constraint = Label.parseCanonicalUnchecked(""//constraint""); // TODO(https://github.com/bazelbuild/bazel/issues/14726): Add tests of optional toolchains. ruleClassBuilder.addExecGroups( ImmutableMap.of( ""cherry"", ExecGroup.builder() .addToolchainType(ToolchainTypeRequirement.create(toolchain)) .execCompatibleWith(ImmutableSet.of(constraint)) .copyFrom(null) .build())); RuleClass ruleClass = ruleClassBuilder.build(); assertThat(ruleClass.getExecGroups()).hasSize(1); assertThat(ruleClass.getExecGroups().get(""cherry"")).hasToolchainType(toolchain); assertThat(ruleClass.getExecGroups().get(""cherry"")).toolchainType(toolchain).isMandatory(); assertThat(ruleClass.getExecGroups().get(""cherry"")).hasExecCompatibleWith(constraint); } @Test public void testBuildSetting_createsDefaultAttribute() { RuleClass labelFlag = new RuleClass.Builder(""label_flag"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)) .setBuildSetting(BuildSetting.create(true, NODEP_LABEL)) .build(); RuleClass stringSetting = new RuleClass.Builder(""string_setting"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)) .setBuildSetting(BuildSetting.create(false, STRING)) .build(); assertThat(labelFlag.hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, NODEP_LABEL)).isTrue(); assertThat(stringSetting.hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, STRING)).isTrue(); } @Test public void testBuildSetting_doesNotCreateDefaultAttributeIfNotBuildSetting() { RuleClass stringSetting = new RuleClass.Builder(""non_build_setting"", RuleClassType.NORMAL, false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)) .build(); assertThat(stringSetting.hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, LABEL)).isFalse(); } @Test public void testBuildTooManyAttributesRejected() { RuleClass.Builder builder = new RuleClass.Builder(""myclass"", RuleClassType.NORMAL, /*starlark=*/ false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)); for (int i = 0; i < 200; i++) { builder.add(attr(""attr"" + i, STRING)); } IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, builder::build); assertThat(expected) .hasMessageThat() .isEqualTo(""Rule class myclass declared too many attributes (202 > 200)""); } @Test public void testBuildTooLongAttributeNameRejected() { IllegalArgumentException expected = assertThrows( IllegalArgumentException.class, () -> new RuleClass.Builder(""myclass"", RuleClassType.NORMAL, /*starlark=*/ false) .factory(DUMMY_CONFIGURED_TARGET_FACTORY) .add(attr(""tags"", STRING_LIST)) .add(attr(""x"".repeat(150), STRING)) .build()); assertThat(expected) .hasMessageThat() .matches(""Attribute myclass\\.x{150}'s name is too long \\(150 > 128\\)""); } } ","implicitOutputsFunction " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.backends.lwjgl3; import java.io.File; import java.io.PrintStream; import java.lang.reflect.Method; import java.nio.IntBuffer; import com.badlogic.gdx.ApplicationLogger; import com.badlogic.gdx.backends.lwjgl3.audio.Lwjgl3Audio; import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio; import com.badlogic.gdx.graphics.glutils.GLVersion; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.opengl.AMDDebugOutput; import org.lwjgl.opengl.ARBDebugOutput; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL43; import org.lwjgl.opengl.GLCapabilities; import org.lwjgl.opengl.GLUtil; import org.lwjgl.opengl.KHRDebug; import org.lwjgl.system.Callback; import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Audio; import com.badlogic.gdx.Files; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Graphics; import com.badlogic.gdx.Input; import com.badlogic.gdx.LifecycleListener; import com.badlogic.gdx.Net; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.backends.lwjgl3.audio.mock.MockAudio; import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Clipboard; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.SharedLibraryLoader; import org.lwjgl.system.Configuration; public class Lwjgl3Application implements Lwjgl3ApplicationBase { private final Lwjgl3ApplicationConfiguration config; final Array windows = new Array(); private volatile Lwjgl3Window currentWindow; private Lwjgl3Audio audio; private final Files files; private final Net net; private final ObjectMap preferences = new ObjectMap(); private final Lwjgl3Clipboard clipboard; private int logLevel = LOG_INFO; private ApplicationLogger applicationLogger; private volatile boolean running = true; private final Array runnables = new Array(); private final Array executedRunnables = new Array(); private final Array lifecycleListeners = new Array(); private static GLFWErrorCallback errorCallback; private static GLVersion glVersion; private static Callback glDebugCallback; private final Sync sync; static void initializeGlfw () { if (errorCallback == null) { if (SharedLibraryLoader.isMac) loadGlfwAwtMacos(); Lwjgl3NativesLoader.load(); errorCallback = GLFWErrorCallback.createPrint(Lwjgl3ApplicationConfiguration.errorStream); GLFW.glfwSetErrorCallback(errorCallback); if (SharedLibraryLoader.isMac) GLFW.glfwInitHint(GLFW.GLFW_ANGLE_PLATFORM_TYPE, GLFW.GLFW_ANGLE_PLATFORM_TYPE_METAL); GLFW.glfwInitHint(GLFW.GLFW_JOYSTICK_HAT_BUTTONS, GLFW.GLFW_FALSE); if (!GLFW.glfwInit()) { throw new GdxRuntimeException(""Unable to initialize GLFW""); } } } static void loadANGLE () { try { Class angleLoader = Class.forName(""com.badlogic.gdx.backends.lwjgl3.angle.ANGLELoader""); Method load = angleLoader.getMethod(""load""); load.invoke(angleLoader); } catch (ClassNotFoundException t) { return; } catch (Throwable t) { throw new GdxRuntimeException(""Couldn't load ANGLE."", t); } } static void postLoadANGLE () { try { Class angleLoader = Class.forName(""com.badlogic.gdx.backends.lwjgl3.angle.ANGLELoader""); Method load = angleLoader.getMethod(""postGlfwInit""); load.invoke(angleLoader); } catch (ClassNotFoundException t) { return; } catch (Throwable t) { throw new GdxRuntimeException(""Couldn't load ANGLE."", t); } } static void loadGlfwAwtMacos () { try { Class loader = Class.forName(""com.badlogic.gdx.backends.lwjgl3.awt.GlfwAWTLoader""); Method load = loader.getMethod(""load""); File sharedLib = (File)load.invoke(loader); Configuration.GLFW_LIBRARY_NAME.set(sharedLib.getAbsolutePath()); Configuration.GLFW_CHECK_THREAD0.set(false); } catch (ClassNotFoundException t) { return; } catch (Throwable t) { throw new GdxRuntimeException(""Couldn't load GLFW AWT for macOS."", t); } } public Lwjgl3Application (ApplicationListener listener) { this(listener, new Lwjgl3ApplicationConfiguration()); } public Lwjgl3Application (ApplicationListener listener, Lwjgl3ApplicationConfiguration config) { if (config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20) loadANGLE(); initializeGlfw(); setApplicationLogger(new Lwjgl3ApplicationLogger()); this.config = config = Lwjgl3ApplicationConfiguration.copy(config); if (config.title == null) config.title = listener.getClass().getSimpleName(); Gdx.app = this; if (!config.disableAudio) { try { this.audio = createAudio(config); } catch (Throwable t) { log(""Lwjgl3Application"", ""Couldn't initialize audio, disabling audio"", t); this.audio = new MockAudio(); } } else { this.audio = new MockAudio(); } Gdx.audio = audio; this.files = Gdx.files = createFiles(); this.net = Gdx.net = new Lwjgl3Net(config); this.clipboard = new Lwjgl3Clipboard(); this.sync = new Sync(); Lwjgl3Window window = createWindow(config, listener, 0); if (config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20) postLoadANGLE(); windows.add(window); try { loop(); cleanupWindows(); } catch (Throwable t) { if (t instanceof RuntimeException) throw (RuntimeException)t; else throw new GdxRuntimeException(t); } finally { cleanup(); } } protected void loop () { Array closedWindows = new Array(); while (running && windows.size > 0) { // FIXME put it on a separate thread audio.update(); boolean haveWindowsRendered = false; closedWindows.clear(); int targetFramerate = -2; for (Lwjgl3Window window : windows) { window.makeCurrent(); currentWindow = window; if (targetFramerate == -2) targetFramerate = window.getConfig().foregroundFPS; synchronized (lifecycleListeners) { haveWindowsRendered |= window.update(); } if (window.shouldClose()) { closedWindows.add(window); } } GLFW.glfwPollEvents(); boolean shouldRequestRendering; synchronized (runnables) { shouldRequestRendering = runnables.size > 0; executedRunnables.clear(); executedRunnables.addAll(runnables); runnables.clear(); } for (Runnable runnable : executedRunnables) { runnable.run(); } if (shouldRequestRendering) { // Must follow Runnables execution so changes done by Runnables are reflected // in the following render. for (Lwjgl3Window window : windows) { if (!window.getGraphics().isContinuousRendering()) window.requestRendering(); } } for (Lwjgl3Window closedWindow : closedWindows) { if (windows.size == 1) { // Lifecycle listener methods have to be called before ApplicationListener methods. The // application will be disposed when _all_ windows have been disposed, which is the case, // when there is only 1 window left, which is in the process of being disposed. for (int i = lifecycleListeners.size - 1; i >= 0; i--) { LifecycleListener l = lifecycleListeners.get(i); l.pause(); l.dispose(); } lifecycleListeners.clear(); } closedWindow.dispose(); windows.removeValue(closedWindow, false); } if (!haveWindowsRendered) { // Sleep a few milliseconds in case no rendering was requested // with continuous rendering disabled. try { Thread.sleep(1000 / config.idleFPS); } catch (InterruptedException e) { // ignore } } else if (targetFramerate > 0) { sync.sync(targetFramerate); // sleep as needed to meet the target framerate } } } protected void cleanupWindows () { synchronized (lifecycleListeners) { for (LifecycleListener lifecycleListener : lifecycleListeners) { lifecycleListener.pause(); lifecycleListener.dispose(); } } for (Lwjgl3Window window : windows) { window.dispose(); } windows.clear(); } protected void cleanup () { Lwjgl3Cursor.disposeSystemCursors(); audio.dispose(); errorCallback.free(); errorCallback = null; if (glDebugCallback != null) { glDebugCallback.free(); glDebugCallback = null; } GLFW.glfwTerminate(); } @Override public ApplicationListener getApplicationListener () { return currentWindow.getListener(); } @Override public Graphics getGraphics () { return currentWindow.getGraphics(); } @Override public Audio getAudio () { return audio; } @Override public Input getInput () { return currentWindow.getInput(); } @Override public Files getFiles () { return files; } @Override public Net getNet () { return net; } @Override public void debug (String tag, String message) { if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message); } @Override public void debug (String tag, String message, Throwable exception) { if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message, exception); } @Override public void log (String tag, String message) { if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message); } @Override public void log (String tag, String message, Throwable exception) { if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message, exception); } @Override public void error (String tag, String message) { if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message); } @Override public void error (String tag, String message, Throwable exception) { if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message, exception); } @Override public void setLogLevel (int logLevel) { this.logLevel = logLevel; } @Override public int getLogLevel () { return logLevel; } @Override public void setApplicationLogger (ApplicationLogger applicationLogger) { this.applicationLogger = applicationLogger; } @Override public ApplicationLogger getApplicationLogger () { return applicationLogger; } @Override public ApplicationType getType () { return ApplicationType.Desktop; } @Override public int getVersion () { return 0; } @Override public long getJavaHeap () { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } @Override public long getNativeHeap () { return getJavaHeap(); } @Override public Preferences getPreferences (String name) { if (preferences.containsKey(name)) { return preferences.get(name); } else { Preferences prefs = new Lwjgl3Preferences( new Lwjgl3FileHandle(new File(config.preferencesDirectory, name), config.preferencesFileType)); preferences.put(name, prefs); return prefs; } } @Override public Clipboard getClipboard () { return clipboard; } @Override public void postRunnable (Runnable runnable) { synchronized (runnables) { runnables.add(runnable); } } @Override public void exit () { running = false; } @Override public void addLifecycleListener (LifecycleListener listener) { synchronized (lifecycleListeners) { lifecycleListeners.add(listener); } } @Override public void removeLifecycleListener (LifecycleListener listener) { synchronized (lifecycleListeners) { lifecycleListeners.removeValue(listener, true); } } @Override public Lwjgl3Audio createAudio (Lwjgl3ApplicationConfiguration config) { return new OpenALLwjgl3Audio(config.audioDeviceSimultaneousSources, config.audioDeviceBufferCount, config.audioDeviceBufferSize); } @Override public Lwjgl3Input createInput (Lwjgl3Window window) { return new DefaultLwjgl3Input(window); } protected Files createFiles () { return new Lwjgl3Files(); } /** Creates a new {@link Lwjgl3Window} using the provided listener and {@link Lwjgl3WindowConfiguration}. * * This function only just instantiates a {@link Lwjgl3Window} and returns immediately. The actual window creation is postponed * with {@link Application#postRunnable(Runnable)} until after all existing windows are updated. */ public Lwjgl3Window newWindow (ApplicationListener listener, Lwjgl3WindowConfiguration config) { Lwjgl3ApplicationConfiguration appConfig = Lwjgl3ApplicationConfiguration.copy(this.config); appConfig.setWindowConfiguration(config); if (appConfig.title == null) appConfig.title = listener.getClass().getSimpleName(); return createWindow(appConfig, listener, windows.get(0).getWindowHandle()); } private Lwjgl3Window createWindow (final Lwjgl3ApplicationConfiguration config, ApplicationListener listener, final long sharedContext) { final Lwjgl3Window window = new Lwjgl3Window(listener, config, this); if (sharedContext == 0) { // the main window is created immediately createWindow(window, config, sharedContext); } else { // creation of additional windows is deferred to avoid GL context trouble postRunnable(new Runnable() { public void run () { createWindow(window, config, sharedContext); windows.add(window); } }); } return window; } void createWindow (Lwjgl3Window window, Lwjgl3ApplicationConfiguration config, long sharedContext) { long windowHandle = createGlfwWindow(config, sharedContext); window.create(windowHandle); window.setVisible(config.initialVisible); for (int i = 0; i < 2; i++) { GL11.glClearColor(config.initialBackgroundColor.r, config.initialBackgroundColor.g, config.initialBackgroundColor.b, config.initialBackgroundColor.a); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); GLFW.glfwSwapBuffers(windowHandle); } } static long createGlfwWindow (Lwjgl3ApplicationConfiguration config, long sharedContextWindow) { GLFW.glfwDefaultWindowHints(); GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, config.windowResizable ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE); GLFW.glfwWindowHint(GLFW.GLFW_MAXIMIZED, config.windowMaximized ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE); GLFW.glfwWindowHint(GLFW.GLFW_AUTO_ICONIFY, config.autoIconify ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE); GLFW.glfwWindowHint(GLFW.GLFW_RED_BITS, config.r); GLFW.glfwWindowHint(GLFW.GLFW_GREEN_BITS, config.g); GLFW.glfwWindowHint(GLFW.GLFW_BLUE_BITS, config.b); GLFW.glfwWindowHint(GLFW.GLFW_ALPHA_BITS, config.a); GLFW.glfwWindowHint(GLFW.GLFW_STENCIL_BITS, config.stencil); GLFW.glfwWindowHint(GLFW.GLFW_DEPTH_BITS, config.depth); GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, config.samples); if (config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.GL30 || config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.GL31 || config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.GL32) { GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, config.gles30ContextMajorVersion); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, config.gles30ContextMinorVersion); if (SharedLibraryLoader.isMac) { // hints mandatory on OS X for GL 3.2+ context creation, but fail on Windows if the // WGL_ARB_create_context extension is not available // see: http://www.glfw.org/docs/latest/compat.html GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE); GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); } } else { if (config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20) { GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_CREATION_API, GLFW.GLFW_EGL_CONTEXT_API); GLFW.glfwWindowHint(GLFW.GLFW_CLIENT_API, GLFW.GLFW_OPENGL_ES_API); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 2); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 0); } } if (config.transparentFramebuffer) { GLFW.glfwWindowHint(GLFW.GLFW_TRANSPARENT_FRAMEBUFFER, GLFW.GLFW_TRUE); } if (config.debug) { GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_DEBUG_CONTEXT, GLFW.GLFW_TRUE); } long windowHandle = 0; if (config.fullscreenMode != null) { GLFW.glfwWindowHint(GLFW.GLFW_REFRESH_RATE, config.fullscreenMode.refreshRate); windowHandle = GLFW.glfwCreateWindow(config.fullscreenMode.width, config.fullscreenMode.height, config.title, config.fullscreenMode.getMonitor(), sharedContextWindow); } else { GLFW.glfwWindowHint(GLFW.GLFW_DECORATED, config.windowDecorated ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE); windowHandle = GLFW.glfwCreateWindow(config.windowWidth, config.windowHeight, config.title, 0, sharedContextWindow); } if (windowHandle == 0) { throw new GdxRuntimeException(""Couldn't create window""); } Lwjgl3Window.setSizeLimits(windowHandle, config.windowMinWidth, config.windowMinHeight, config.windowMaxWidth, config.windowMaxHeight); if (config.fullscreenMode == null) { if (config.windowX == -1 && config.windowY == -1) { // i.e., center the window int windowWidth = Math.max(config.windowWidth, config.windowMinWidth); int windowHeight = Math.max(config.windowHeight, config.windowMinHeight); if (config.windowMaxWidth > -1) windowWidth = Math.min(windowWidth, config.windowMaxWidth); if (config.windowMaxHeight > -1) windowHeight = Math.min(windowHeight, config.windowMaxHeight); long monitorHandle = GLFW.glfwGetPrimaryMonitor(); if (config.windowMaximized && config.maximizedMonitor != null) { monitorHandle = config.maximizedMonitor.monitorHandle; } GridPoint2 [MASK] = Lwjgl3ApplicationConfiguration.calculateCenteredWindowPosition( Lwjgl3ApplicationConfiguration.toLwjgl3Monitor(monitorHandle), windowWidth, windowHeight); GLFW.glfwSetWindowPos(windowHandle, [MASK] .x, [MASK] .y); } else { GLFW.glfwSetWindowPos(windowHandle, config.windowX, config.windowY); } if (config.windowMaximized) { GLFW.glfwMaximizeWindow(windowHandle); } } if (config.windowIconPaths != null) { Lwjgl3Window.setIcon(windowHandle, config.windowIconPaths, config.windowIconFileType); } GLFW.glfwMakeContextCurrent(windowHandle); GLFW.glfwSwapInterval(config.vSyncEnabled ? 1 : 0); if (config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20) { try { Class gles = Class.forName(""org.lwjgl.opengles.GLES""); gles.getMethod(""createCapabilities"").invoke(gles); } catch (Throwable e) { throw new GdxRuntimeException(""Couldn't initialize GLES"", e); } } else { GL.createCapabilities(); } initiateGL(config.glEmulation == Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20); if (!glVersion.isVersionEqualToOrHigher(2, 0)) throw new GdxRuntimeException(""OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: "" + GL11.glGetString(GL11.GL_VERSION) + ""\n"" + glVersion.getDebugVersionString()); if (config.glEmulation != Lwjgl3ApplicationConfiguration.GLEmulation.ANGLE_GLES20 && !supportsFBO()) { throw new GdxRuntimeException(""OpenGL 2.0 or higher with the FBO extension is required. OpenGL version: "" + GL11.glGetString(GL11.GL_VERSION) + "", FBO extension: false\n"" + glVersion.getDebugVersionString()); } if (config.debug) { glDebugCallback = GLUtil.setupDebugMessageCallback(config.debugStream); setGLDebugMessageControl(GLDebugMessageSeverity.NOTIFICATION, false); } return windowHandle; } private static void initiateGL (boolean useGLES20) { if (!useGLES20) { String versionString = GL11.glGetString(GL11.GL_VERSION); String vendorString = GL11.glGetString(GL11.GL_VENDOR); String rendererString = GL11.glGetString(GL11.GL_RENDERER); glVersion = new GLVersion(Application.ApplicationType.Desktop, versionString, vendorString, rendererString); } else { try { Class gles = Class.forName(""org.lwjgl.opengles.GLES20""); Method getString = gles.getMethod(""glGetString"", int.class); String versionString = (String)getString.invoke(gles, GL11.GL_VERSION); String vendorString = (String)getString.invoke(gles, GL11.GL_VENDOR); String rendererString = (String)getString.invoke(gles, GL11.GL_RENDERER); glVersion = new GLVersion(Application.ApplicationType.Desktop, versionString, vendorString, rendererString); } catch (Throwable e) { throw new GdxRuntimeException(""Couldn't get GLES version string."", e); } } } private static boolean supportsFBO () { // FBO is in core since OpenGL 3.0, see https://www.opengl.org/wiki/Framebuffer_Object return glVersion.isVersionEqualToOrHigher(3, 0) || GLFW.glfwExtensionSupported(""GL_EXT_framebuffer_object"") || GLFW.glfwExtensionSupported(""GL_ARB_framebuffer_object""); } public enum GLDebugMessageSeverity { HIGH(GL43.GL_DEBUG_SEVERITY_HIGH, KHRDebug.GL_DEBUG_SEVERITY_HIGH, ARBDebugOutput.GL_DEBUG_SEVERITY_HIGH_ARB, AMDDebugOutput.GL_DEBUG_SEVERITY_HIGH_AMD), MEDIUM(GL43.GL_DEBUG_SEVERITY_MEDIUM, KHRDebug.GL_DEBUG_SEVERITY_MEDIUM, ARBDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_ARB, AMDDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_AMD), LOW( GL43.GL_DEBUG_SEVERITY_LOW, KHRDebug.GL_DEBUG_SEVERITY_LOW, ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB, AMDDebugOutput.GL_DEBUG_SEVERITY_LOW_AMD), NOTIFICATION(GL43.GL_DEBUG_SEVERITY_NOTIFICATION, KHRDebug.GL_DEBUG_SEVERITY_NOTIFICATION, -1, -1); final int gl43, khr, arb, amd; GLDebugMessageSeverity (int gl43, int khr, int arb, int amd) { this.gl43 = gl43; this.khr = khr; this.arb = arb; this.amd = amd; } } /** Enables or disables GL debug messages for the specified severity level. Returns false if the severity level could not be * set (e.g. the NOTIFICATION level is not supported by the ARB and AMD extensions). * * See {@link Lwjgl3ApplicationConfiguration#enableGLDebugOutput(boolean, PrintStream)} */ public static boolean setGLDebugMessageControl (GLDebugMessageSeverity severity, boolean enabled) { GLCapabilities caps = GL.getCapabilities(); final int GL_DONT_CARE = 0x1100; // not defined anywhere yet if (caps.OpenGL43) { GL43.glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, severity.gl43, (IntBuffer)null, enabled); return true; } if (caps.GL_KHR_debug) { KHRDebug.glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, severity.khr, (IntBuffer)null, enabled); return true; } if (caps.GL_ARB_debug_output && severity.arb != -1) { ARBDebugOutput.glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, severity.arb, (IntBuffer)null, enabled); return true; } if (caps.GL_AMD_debug_output && severity.amd != -1) { AMDDebugOutput.glDebugMessageEnableAMD(GL_DONT_CARE, severity.amd, (IntBuffer)null, enabled); return true; } return false; } } ","newPos " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.metadata.id3; import static com.google.android.exoplayer2.util.Util.castNonNull; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; /** * Comment ID3 frame. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public final class CommentFrame extends Id3Frame { public static final String ID = ""COMM""; public final String language; public final String description; public final String text; public CommentFrame(String language, String description, String text) { super(ID); this.language = language; this.description = description; this.text = text; } /* package */ CommentFrame(Parcel in) { super(ID); language = castNonNull(in.readString()); description = castNonNull(in.readString()); text = castNonNull(in.readString()); } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } CommentFrame other = (CommentFrame) obj; return Util.areEqual(description, other.description) && Util.areEqual(language, other.language) && Util.areEqual(text, other.text); } @Override public int hashCode() { int result = 17; result = 31 * result + (language != null ? language.hashCode() : 0); result = 31 * result + (description != null ? description.hashCode() : 0); result = 31 * result + (text != null ? text.hashCode() : 0); return result; } @Override public String toString() { return id + "": language="" + language + "", description="" + description; } // Parcelable implementation. @Override public void writeToParcel(Parcel dest, int [MASK] ) { dest.writeString(id); dest.writeString(language); dest.writeString(text); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public CommentFrame createFromParcel(Parcel in) { return new CommentFrame(in); } @Override public CommentFrame[] newArray(int size) { return new CommentFrame[size]; } }; } ","flags " "/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: NodeSequence.java 469367 2006-10-31 04:41:08Z minchau $ */ package org.apache.xpath.axes; import java.util.Vector; import org.apache.xml.dtm.DTM; import org.apache.xml.dtm.DTMFilter; import org.apache.xml.dtm.DTMIterator; import org.apache.xml.dtm.DTMManager; import org.apache.xml.utils.NodeVector; import org.apache.xpath.NodeSetDTM; import org.apache.xpath.XPathContext; import org.apache.xpath.objects.XObject; /** * This class is the dynamic wrapper for a Xalan DTMIterator instance, and * provides random access capabilities. */ public class NodeSequence extends XObject implements DTMIterator, Cloneable, PathComponent { static final long serialVersionUID = 3866261934726581044L; /** The index of the last node in the iteration. */ protected int m_last = -1; /** * The index of the next node to be fetched. Useful if this * is a cached iterator, and is being used as random access * NodeList. */ protected int m_next = 0; /** * A cache of a list of nodes obtained from the iterator so far. * This list is appended to until the iterator is exhausted and * the cache is complete. *

* Multiple NodeSequence objects may share the same cache. */ private IteratorCache m_cache; /** * If this iterator needs to cache nodes that are fetched, they * are stored in the Vector in the generic object. */ protected NodeVector getVector() { NodeVector nv = (m_cache != null) ? m_cache.getVector() : null; return nv; } /** * Get the cache (if any) of nodes obtained from * the iterator so far. Note that the cache keeps * growing until the iterator is walked to exhaustion, * at which point the cache is ""complete"". */ private IteratorCache getCache() { return m_cache; } /** * Set the vector where nodes will be cached. */ protected void SetVector(NodeVector v) { setObject(v); } /** * If the iterator needs to cache nodes as they are fetched, * then this method returns true. */ public boolean hasCache() { final NodeVector nv = getVector(); return (nv != null); } /** * If this NodeSequence has a cache, and that cache is * fully populated then this method returns true, otherwise * if there is no cache or it is not complete it returns false. */ private boolean cacheComplete() { final boolean complete; if (m_cache != null) { complete = m_cache.isComplete(); } else { complete = false; } return complete; } /** * If this NodeSequence has a cache, mark that it is complete. * This method should be called after the iterator is exhausted. */ private void markCacheComplete() { NodeVector nv = getVector(); if (nv != null) { m_cache.setCacheComplete(true); } } /** * The functional iterator that fetches nodes. */ protected DTMIterator m_iter; /** * Set the functional iterator that fetches nodes. * @param iter The iterator that is to be contained. */ public final void setIter(DTMIterator iter) { m_iter = iter; } /** * Get the functional iterator that fetches nodes. * @return The contained iterator. */ public final DTMIterator getContainedIter() { return m_iter; } /** * The DTMManager to use if we're using a NodeVector only. * We may well want to do away with this, and store it in the NodeVector. */ protected DTMManager m_dtmMgr; // ==== Constructors ==== /** * Create a new NodeSequence from a (already cloned) iterator. * * @param iter Cloned (not static) DTMIterator. * @param context The initial context node. * @param xctxt The execution context. * @param shouldCacheNodes True if this sequence can random access. */ private NodeSequence(DTMIterator iter, int context, XPathContext xctxt, boolean shouldCacheNodes) { setIter(iter); setRoot(context, xctxt); setShouldCacheNodes(shouldCacheNodes); } /** * Create a new NodeSequence from a (already cloned) iterator. * * @param nodeVector */ public NodeSequence(Object nodeVector) { super(nodeVector); if (nodeVector instanceof NodeVector) { SetVector((NodeVector) nodeVector); } if(null != nodeVector) { assertion(nodeVector instanceof NodeVector, ""Must have a NodeVector as the object for NodeSequence!""); if(nodeVector instanceof DTMIterator) { setIter((DTMIterator)nodeVector); m_last = ((DTMIterator)nodeVector).getLength(); } } } /** * Construct an empty XNodeSet object. This is used to create a mutable * nodeset to which random nodes may be added. */ private NodeSequence(DTMManager dtmMgr) { super(new NodeVector()); m_last = 0; m_dtmMgr = dtmMgr; } /** * Create a new NodeSequence in an invalid (null) state. */ public NodeSequence() { return; } /** * @see DTMIterator#getDTM(int) */ public DTM getDTM(int nodeHandle) { DTMManager mgr = getDTMManager(); if(null != mgr) return getDTMManager().getDTM(nodeHandle); else { assertion(false, ""Can not get a DTM Unless a DTMManager has been set!""); return null; } } /** * @see DTMIterator#getDTMManager() */ public DTMManager getDTMManager() { return m_dtmMgr; } /** * @see DTMIterator#getRoot() */ public int getRoot() { if(null != m_iter) return m_iter.getRoot(); else { // NodeSetDTM will call this, and so it's not a good thing to throw // an assertion here. // assertion(false, ""Can not get the root from a non-iterated NodeSequence!""); return DTM.NULL; } } /** * @see DTMIterator#setRoot(int, Object) */ public void setRoot(int nodeHandle, Object environment) { if(null != m_iter) { XPathContext xctxt = (XPathContext)environment; m_dtmMgr = xctxt.getDTMManager(); m_iter.setRoot(nodeHandle, environment); if(!m_iter.isDocOrdered()) { if(!hasCache()) setShouldCacheNodes(true); runTo(-1); m_next=0; } } else assertion(false, ""Can not setRoot on a non-iterated NodeSequence!""); } /** * @see DTMIterator#reset() */ public void reset() { m_next = 0; // not resetting the iterator on purpose!!! } /** * @see DTMIterator#getWhatToShow() */ public int getWhatToShow() { return hasCache() ? (DTMFilter.SHOW_ALL & ~DTMFilter.SHOW_ENTITY_REFERENCE) : m_iter.getWhatToShow(); } /** * @see DTMIterator#getExpandEntityReferences() */ public boolean getExpandEntityReferences() { if(null != m_iter) return m_iter.getExpandEntityReferences(); else return true; } /** * @see DTMIterator#nextNode() */ public int nextNode() { // If the cache is on, and the node has already been found, then // just return from the list. NodeVector vec = getVector(); if (null != vec) { // There is a cache if(m_next < vec.size()) { // The node is in the cache, so just return it. int next = vec.elementAt(m_next); m_next++; return next; } else if(cacheComplete() || (-1 != m_last) || (null == m_iter)) { m_next++; return DTM.NULL; } } if (null == m_iter) return DTM.NULL; int next = m_iter.nextNode(); if(DTM.NULL != next) { if(hasCache()) { if(m_iter.isDocOrdered()) { getVector().addElement(next); m_next++; } else { int insertIndex = addNodeInDocOrder(next); if(insertIndex >= 0) m_next++; } } else m_next++; } else { // We have exhausted the iterator, and if there is a cache // it must have all nodes in it by now, so let the cache // know that it is complete. markCacheComplete(); m_last = m_next; m_next++; } return next; } /** * @see DTMIterator#previousNode() */ public int previousNode() { if(hasCache()) { if(m_next <= 0) return DTM.NULL; else { m_next--; return item(m_next); } } else { int n = m_iter.previousNode(); m_next = m_iter.getCurrentPos(); return m_next; } } /** * @see DTMIterator#detach() */ public void detach() { if(null != m_iter) m_iter.detach(); super.detach(); } /** * Calling this with a value of false will cause the nodeset * to be cached. * @see DTMIterator#allowDetachToRelease(boolean) */ public void allowDetachToRelease(boolean allowRelease) { if((false == allowRelease) && !hasCache()) { setShouldCacheNodes(true); } if(null != m_iter) m_iter.allowDetachToRelease(allowRelease); super.allowDetachToRelease(allowRelease); } /** * @see DTMIterator#getCurrentNode() */ public int getCurrentNode() { if(hasCache()) { int currentIndex = m_next-1; NodeVector vec = getVector(); if((currentIndex >= 0) && (currentIndex < vec.size())) return vec.elementAt(currentIndex); else return DTM.NULL; } if(null != m_iter) { return m_iter.getCurrentNode(); } else return DTM.NULL; } /** * @see DTMIterator#isFresh() */ public boolean isFresh() { return (0 == m_next); } /** * @see DTMIterator#setShouldCacheNodes(boolean) */ public void setShouldCacheNodes(boolean b) { if (b) { if(!hasCache()) { SetVector(new NodeVector()); } // else // getVector().RemoveAllNoClear(); // Is this good? } else SetVector(null); } /** * @see DTMIterator#isMutable() */ public boolean isMutable() { return hasCache(); // though may be surprising if it also has an iterator! } /** * @see DTMIterator#getCurrentPos() */ public int getCurrentPos() { return m_next; } /** * @see DTMIterator#runTo(int) */ public void runTo(int index) { int n; if (-1 == index) { int pos = m_next; while (DTM.NULL != (n = nextNode())); m_next = pos; } else if(m_next == index) { return; } else if(hasCache() && m_next < getVector().size()) { m_next = index; } else if((null == getVector()) && (index < m_next)) { while ((m_next >= index) && DTM.NULL != (n = previousNode())); } else { while ((m_next < index) && DTM.NULL != (n = nextNode())); } } /** * @see DTMIterator#setCurrentPos(int) */ public void setCurrentPos(int i) { runTo(i); } /** * @see DTMIterator#item(int) */ public int item(int index) { setCurrentPos(index); int n = nextNode(); m_next = index; return n; } /** * @see DTMIterator#setItem(int, int) */ public void setItem(int node, int index) { NodeVector vec = getVector(); if(null != vec) { int [MASK] = vec.elementAt(index); if ( [MASK] != node && m_cache.useCount() > 1) { /* If we are going to set the node at the given index * to a different value, and the cache is shared * (has a use count greater than 1) * then make a copy of the cache and use it * so we don't overwrite the value for other * users of the cache. */ IteratorCache newCache = new IteratorCache(); final NodeVector nv; try { nv = (NodeVector) vec.clone(); } catch (CloneNotSupportedException e) { // This should never happen e.printStackTrace(); RuntimeException rte = new RuntimeException(e.getMessage()); throw rte; } newCache.setVector(nv); newCache.setCacheComplete(true); m_cache = newCache; vec = nv; // Keep our superclass informed of the current NodeVector super.setObject(nv); /* When we get to here the new cache has * a use count of 1 and when setting a * bunch of values on the same NodeSequence, * such as when sorting, we will keep setting * values in that same copy which has a use count of 1. */ } vec.setElementAt(node, index); m_last = vec.size(); } else m_iter.setItem(node, index); } /** * @see DTMIterator#getLength() */ public int getLength() { IteratorCache cache = getCache(); if(cache != null) { // Nodes from the iterator are cached if (cache.isComplete()) { // All of the nodes from the iterator are cached // so just return the number of nodes in the cache NodeVector nv = cache.getVector(); return nv.size(); } // If this NodeSequence wraps a mutable nodeset, then // m_last will not reflect the size of the nodeset if // it has been mutated... if (m_iter instanceof NodeSetDTM) { return m_iter.getLength(); } if(-1 == m_last) { int pos = m_next; runTo(-1); m_next = pos; } return m_last; } else { return (-1 == m_last) ? (m_last = m_iter.getLength()) : m_last; } } /** * Note: Not a deep clone. * @see DTMIterator#cloneWithReset() */ public DTMIterator cloneWithReset() throws CloneNotSupportedException { NodeSequence seq = (NodeSequence)super.clone(); seq.m_next = 0; if (m_cache != null) { // In making this clone of an iterator we are making // another NodeSequence object it has a reference // to the same IteratorCache object as the original // so we need to remember that more than one // NodeSequence object shares the cache. m_cache.increaseUseCount(); } return seq; } /** * Get a clone of this iterator, but don't reset the iteration in the * process, so that it may be used from the current position. * Note: Not a deep clone. * * @return A clone of this object. * * @throws CloneNotSupportedException */ public Object clone() throws CloneNotSupportedException { NodeSequence clone = (NodeSequence) super.clone(); if (null != m_iter) clone.m_iter = (DTMIterator) m_iter.clone(); if (m_cache != null) { // In making this clone of an iterator we are making // another NodeSequence object it has a reference // to the same IteratorCache object as the original // so we need to remember that more than one // NodeSequence object shares the cache. m_cache.increaseUseCount(); } return clone; } /** * @see DTMIterator#isDocOrdered() */ public boolean isDocOrdered() { if(null != m_iter) return m_iter.isDocOrdered(); else return true; // can't be sure? } /** * @see DTMIterator#getAxis() */ public int getAxis() { if(null != m_iter) return m_iter.getAxis(); else { assertion(false, ""Can not getAxis from a non-iterated node sequence!""); return 0; } } /** * @see PathComponent#getAnalysisBits() */ public int getAnalysisBits() { if((null != m_iter) && (m_iter instanceof PathComponent)) return ((PathComponent)m_iter).getAnalysisBits(); else return 0; } /** * @see org.apache.xpath.Expression#fixupVariables(Vector, int) */ public void fixupVariables(Vector vars, int globalsSize) { super.fixupVariables(vars, globalsSize); } /** * Add the node into a vector of nodes where it should occur in * document order. * @param node The node to be added. * @return insertIndex. * @throws RuntimeException thrown if this NodeSetDTM is not of * a mutable type. */ protected int addNodeInDocOrder(int node) { assertion(hasCache(), ""addNodeInDocOrder must be done on a mutable sequence!""); int insertIndex = -1; NodeVector vec = getVector(); // This needs to do a binary search, but a binary search // is somewhat tough because the sequence test involves // two nodes. int size = vec.size(), i; for (i = size - 1; i >= 0; i--) { int child = vec.elementAt(i); if (child == node) { i = -2; // Duplicate, suppress insert break; } DTM dtm = m_dtmMgr.getDTM(node); if (!dtm.isNodeAfter(node, child)) { break; } } if (i != -2) { insertIndex = i + 1; vec.insertElementAt(node, insertIndex); } // checkDups(); return insertIndex; } // end addNodeInDocOrder(Vector v, Object obj) /** * It used to be that many locations in the code simply * did an assignment to this.m_obj directly, rather than * calling the setObject(Object) method. The problem is * that our super-class would be updated on what the * cache associated with this NodeSequence, but * we wouldn't know ourselves. *

* All setting of m_obj is done through setObject() now, * and this method over-rides the super-class method. * So now we are in the loop have an opportunity * to update some caching information. * */ protected void setObject(Object obj) { if (obj instanceof NodeVector) { // Keep our superclass informed of the current NodeVector // ... if we don't the smoketest fails (don't know why). super.setObject(obj); // A copy of the code of what SetVector() would do. NodeVector v = (NodeVector)obj; if (m_cache != null) { m_cache.setVector(v); } else if (v!=null) { m_cache = new IteratorCache(); m_cache.setVector(v); } } else if (obj instanceof IteratorCache) { IteratorCache cache = (IteratorCache) obj; m_cache = cache; m_cache.increaseUseCount(); // Keep our superclass informed of the current NodeVector super.setObject(cache.getVector()); } else { super.setObject(obj); } } /** * Each NodeSequence object has an iterator which is ""walked"". * As an iterator is walked one obtains nodes from it. * As those nodes are obtained they may be cached, making * the next walking of a copy or clone of the iterator faster. * This field (m_cache) is a reference to such a cache, * which is populated as the iterator is walked. *

* Note that multiple NodeSequence objects may hold a * reference to the same cache, and also * (and this is important) the same iterator. * The iterator and its cache may be shared among * many NodeSequence objects. *

* If one of the NodeSequence objects walks ahead * of the others it fills in the cache. * As the others NodeSequence objects catch up they * get their values from * the cache rather than the iterator itself, so * the iterator is only ever walked once and everyone * benefits from the cache. *

* At some point the cache may be * complete due to walking to the end of one of * the copies of the iterator, and the cache is * then marked as ""complete"". * and the cache will have no more nodes added to it. *

* Its use-count is the number of NodeSequence objects that use it. */ private final static class IteratorCache { /** * A list of nodes already obtained from the iterator. * As the iterator is walked the nodes obtained from * it are appended to this list. *

* Both an iterator and its corresponding cache can * be shared by multiple NodeSequence objects. *

* For example, consider three NodeSequence objects * ns1, ns2 and ns3 doing such sharing, and the * nodes to be obtaind from the iterator being * the sequence { 33, 11, 44, 22, 55 }. *

* If ns3.nextNode() is called 3 times the the * underlying iterator will have walked through * 33, 11, 55 and these three nodes will have been put * in the cache. *

* If ns2.nextNode() is called 2 times it will return * 33 and 11 from the cache, leaving the iterator alone. *

* If ns1.nextNode() is called 6 times it will return * 33 and 11 from the cache, then get 44, 22, 55 from * the iterator, and appending 44, 22, 55 to the cache. * On the sixth call it is found that the iterator is * exhausted and the cache is marked complete. *

* Should ns2 or ns3 have nextNode() called they will * know that the cache is complete, and they will * obtain all subsequent nodes from the cache. *

* Note that the underlying iterator, though shared * is only ever walked once. */ private NodeVector m_vec2; /** * true if the associated iterator is exhausted and * all nodes obtained from it are in the cache. */ private boolean m_isComplete2; private int m_useCount2; IteratorCache() { m_vec2 = null; m_isComplete2 = false; m_useCount2 = 1; return; } /** * Returns count of how many NodeSequence objects share this * IteratorCache object. */ private int useCount() { return m_useCount2; } /** * This method is called when yet another * NodeSequence object uses, or shares * this same cache. * */ private void increaseUseCount() { if (m_vec2 != null) m_useCount2++; } /** * Sets the NodeVector that holds the * growing list of nodes as they are appended * to the cached list. */ private void setVector(NodeVector nv) { m_vec2 = nv; m_useCount2 = 1; } /** * Get the cached list of nodes obtained from * the iterator so far. */ private NodeVector getVector() { return m_vec2; } /** * Call this method with 'true' if the * iterator is exhausted and the cached list * is complete, or no longer growing. */ private void setCacheComplete(boolean b) { m_isComplete2 = b; } /** * Returns true if no cache is complete * and immutable. */ private boolean isComplete() { return m_isComplete2; } } /** * Get the cached list of nodes appended with * values obtained from the iterator as * a NodeSequence is walked when its * nextNode() method is called. */ protected IteratorCache getIteratorCache() { return m_cache; } } ","oldNode " "/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dalvik.system; import dalvik.system.CloseGuard.Reporter; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Provides support for detecting issues found by {@link CloseGuard} from within tests. * *

This is a best effort as it relies on both {@link CloseGuard} being enabled and being able to * force a GC and finalization, none of which are directly controllable by this. * *

This is loaded using reflection by the AbstractResourceLeakageDetectorTestCase class as that * class needs to run on the reference implementation which does not have this class. It implements * {@link Runnable} because that is simpler than trying to manage a specialized interface. * * @hide */ public class CloseGuardMonitor implements Runnable { /** * The {@link Reporter} instance used to receive warnings from {@link CloseGuard}. */ private final Reporter closeGuardReporter; /** * The list of allocation sites that {@link CloseGuard} has reported as not being released. * *

Is thread safe as this will be called during finalization and so there are no guarantees * as to whether it will be called concurrently or not. */ private final List closeGuardAllocationSites = new CopyOnWriteArrayList<>(); /** * Default constructor required for reflection. */ public CloseGuardMonitor() { System.logI(""Creating CloseGuard monitor""); // Save current reporter. closeGuardReporter = CloseGuard.getReporter(); // Override the reporter with our own which collates the allocation sites. CloseGuard.setReporter(new Reporter() { @Override public void report(String message, Throwable allocationSite) { // Ignore message as it's always the same. closeGuardAllocationSites.add(allocationSite); } }); } /** * Check to see whether any resources monitored by {@link CloseGuard} were not released before * they were garbage collected. */ @Override public void run() { // Create a weak reference to an object so that we can detect when it is garbage collected. WeakReference reference = new WeakReference<>(new Object()); try { // 'Force' a GC and finalize to cause CloseGuards to report warnings. Doesn't loop // forever as there are no guarantees that the following code does anything at all so // don't want a potential infinite loop. Runtime runtime = Runtime.getRuntime(); for (int i = 0; i < 20; ++i) { runtime.gc(); System.runFinalization(); try { Thread.sleep(1); } catch (InterruptedException e) { throw new AssertionError(e); } // Check to see if the weak reference has been garbage collected. if (reference.get() == null) { System.logI(""Sentry object has been freed so assuming CloseGuards have reported"" + "" any resource leakages""); break; } } } finally { // Restore the reporter. CloseGuard.setReporter(closeGuardReporter); } if (!closeGuardAllocationSites.isEmpty()) { StringWriter [MASK] = new StringWriter(); PrintWriter printWriter = new PrintWriter( [MASK] ); int i = 0; for (Throwable allocationSite : closeGuardAllocationSites) { printWriter.print(++i); printWriter.print("") ""); allocationSite.printStackTrace(printWriter); printWriter.println("" --------------------------------""); } throw new AssertionError(""Potential resource leakage detected:\n"" + [MASK] ); } } } ","writer " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.common.utils; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static java.util.stream.StreamSupport.stream; import static org.apache.dubbo.common.function.Predicates.and; import static org.apache.dubbo.common.function.Streams.filterAll; import static org.apache.dubbo.common.function.Streams.filterList; import static org.apache.dubbo.common.utils.ClassUtils.getAllInterfaces; import static org.apache.dubbo.common.utils.ClassUtils.getAllSuperClasses; import static org.apache.dubbo.common.utils.ClassUtils.isAssignableFrom; /** * The utilities class for {@link Type} * * @since 2.7.6 */ public interface TypeUtils { Predicate> NON_OBJECT_TYPE_FILTER = t -> !Objects.equals(Object.class, t); static boolean isParameterizedType(Type type) { return type instanceof ParameterizedType; } static Type getRawType(Type type) { if (isParameterizedType(type)) { return ((ParameterizedType) type).getRawType(); } else { return type; } } static Class getRawClass(Type type) { Type rawType = getRawType(type); if (isClass(rawType)) { return (Class) rawType; } return null; } static boolean isClass(Type type) { return type instanceof Class; } static Class findActualTypeArgument(Type type, Class interfaceClass, int index) { return (Class) findActualTypeArguments(type, interfaceClass).get(index); } static List> findActualTypeArguments(Type type, Class interfaceClass) { List> actualTypeArguments = new ArrayList<>(); getAllGenericTypes(type, t -> isAssignableFrom(interfaceClass, getRawClass(t))) .forEach(parameterizedType -> { Class rawClass = getRawClass(parameterizedType); Type[] typeArguments = parameterizedType.getActualTypeArguments(); for (int i = 0; i < typeArguments.length; i++) { Type typeArgument = typeArguments[i]; if (typeArgument instanceof Class) { actualTypeArguments.add(i, (Class) typeArgument); } } Class superClass = rawClass.getSuperclass(); if (superClass != null) { actualTypeArguments.addAll(findActualTypeArguments(superClass, interfaceClass)); } }); return unmodifiableList(actualTypeArguments); } /** * Get the specified [MASK] ' generic [MASK] (including super classes and interfaces) that are assignable from {@link ParameterizedType} interface * * @param type the specified type * @param typeFilters one or more {@link Predicate}s to filter the {@link ParameterizedType} instance * @return non-null read-only {@link List} */ static List getGenericTypes(Type type, Predicate... typeFilters) { Class rawClass = getRawClass(type); if (rawClass == null) { return emptyList(); } List genericTypes = new LinkedList<>(); genericTypes.add(rawClass.getGenericSuperclass()); genericTypes.addAll(asList(rawClass.getGenericInterfaces())); return unmodifiableList( filterList(genericTypes, TypeUtils::isParameterizedType) .stream() .map(ParameterizedType.class::cast) .filter(and(typeFilters)) .collect(toList()) ); } /** * Get all generic [MASK] (including super classes and interfaces) that are assignable from {@link ParameterizedType} interface * * @param type the specified type * @param typeFilters one or more {@link Predicate}s to filter the {@link ParameterizedType} instance * @return non-null read-only {@link List} */ static List getAllGenericTypes(Type type, Predicate... typeFilters) { List allGenericTypes = new LinkedList<>(); // Add generic super classes allGenericTypes.addAll(getAllGenericSuperClasses(type, typeFilters)); // Add generic super interfaces allGenericTypes.addAll(getAllGenericInterfaces(type, typeFilters)); // wrap unmodifiable object return unmodifiableList(allGenericTypes); } /** * Get all generic super classes that are assignable from {@link ParameterizedType} interface * * @param type the specified type * @param typeFilters one or more {@link Predicate}s to filter the {@link ParameterizedType} instance * @return non-null read-only {@link List} */ static List getAllGenericSuperClasses(Type type, Predicate... typeFilters) { Class rawClass = getRawClass(type); if (rawClass == null || rawClass.isInterface()) { return emptyList(); } List> allTypes = new LinkedList<>(); // Add current class allTypes.add(rawClass); // Add all super classes allTypes.addAll(getAllSuperClasses(rawClass, NON_OBJECT_TYPE_FILTER)); List allGenericSuperClasses = allTypes .stream() .map(Class::getGenericSuperclass) .filter(TypeUtils::isParameterizedType) .map(ParameterizedType.class::cast) .collect(Collectors.toList()); return unmodifiableList(filterAll(allGenericSuperClasses, typeFilters)); } /** * Get all generic interfaces that are assignable from {@link ParameterizedType} interface * * @param type the specified type * @param typeFilters one or more {@link Predicate}s to filter the {@link ParameterizedType} instance * @return non-null read-only {@link List} */ static List getAllGenericInterfaces(Type type, Predicate... typeFilters) { Class rawClass = getRawClass(type); if (rawClass == null) { return emptyList(); } List> allTypes = new LinkedList<>(); // Add current class allTypes.add(rawClass); // Add all super classes allTypes.addAll(getAllSuperClasses(rawClass, NON_OBJECT_TYPE_FILTER)); // Add all super interfaces allTypes.addAll(getAllInterfaces(rawClass)); List allGenericInterfaces = allTypes .stream() .map(Class::getGenericInterfaces) .map(Arrays::asList) .flatMap(Collection::stream) .filter(TypeUtils::isParameterizedType) .map(ParameterizedType.class::cast) .collect(toList()); return unmodifiableList(filterAll(allGenericInterfaces, typeFilters)); } static String getClassName(Type type) { return getRawType(type).getTypeName(); } static Set getClassNames(Iterable [MASK] ) { return stream( [MASK] .spliterator(), false) .map(TypeUtils::getClassName) .collect(toSet()); } } ","types " "/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.channel.socket; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelOption; import io.netty.channel.MessageSizeEstimator; import io.netty.channel.RecvByteBufAllocator; import io.netty.channel.WriteBufferWaterMark; import java.net.Socket; import java.net.StandardSocketOptions; /** * A {@link ChannelConfig} for a {@link SocketChannel}. * *

Available options

* * In addition to the options provided by {@link DuplexChannelConfig}, * {@link SocketChannelConfig} allows the following options in the option map: * * * * * * * * * * * * * * * * * * * * * *
NameAssociated setter method
{@link ChannelOption#SO_KEEPALIVE}{@link #setKeepAlive(boolean)}
{@link ChannelOption#SO_REUSEADDR}{@link #setReuseAddress(boolean)}
{@link ChannelOption#SO_LINGER}{@link #setSoLinger(int)}
{@link ChannelOption#TCP_NODELAY}{@link #setTcpNoDelay(boolean)}
{@link ChannelOption#SO_RCVBUF}{@link #setReceiveBufferSize(int)}
{@link ChannelOption#SO_SNDBUF}{@link #setSendBufferSize(int)}
{@link ChannelOption#IP_TOS}{@link #setTrafficClass(int)}
{@link ChannelOption#ALLOW_HALF_CLOSURE}{@link #setAllowHalfClosure(boolean)}
*/ public interface SocketChannelConfig extends DuplexChannelConfig { /** * Gets the {@link StandardSocketOptions#TCP_NODELAY} option. Please note that the default value of this option * is {@code true} unlike the operating system default ({@code false}). However, for some buggy platforms, such as * Android, that shows erratic behavior with Nagle's algorithm disabled, the default value remains to be * {@code false}. */ boolean isTcpNoDelay(); /** * Sets the {@link StandardSocketOptions#TCP_NODELAY} option. Please note that the default value of this option * is {@code true} unlike the operating system default ({@code false}). However, for some buggy platforms, such as * Android, that shows erratic behavior with Nagle's algorithm disabled, the default value remains to be * {@code false}. */ SocketChannelConfig setTcpNoDelay(boolean tcpNoDelay); /** * Gets the {@link StandardSocketOptions#SO_LINGER} option. */ int getSoLinger(); /** * Sets the {@link StandardSocketOptions#SO_LINGER} option. */ SocketChannelConfig setSoLinger(int soLinger); /** * Gets the {@link StandardSocketOptions#SO_SNDBUF} option. */ int getSendBufferSize(); /** * Sets the {@link StandardSocketOptions#SO_SNDBUF} option. */ SocketChannelConfig setSendBufferSize(int sendBufferSize); /** * Gets the {@link StandardSocketOptions#SO_RCVBUF} option. */ int getReceiveBufferSize(); /** * Sets the {@link StandardSocketOptions#SO_RCVBUF} option. */ SocketChannelConfig setReceiveBufferSize(int receiveBufferSize); /** * Gets the {@link StandardSocketOptions#SO_KEEPALIVE} option. */ boolean isKeepAlive(); /** * Sets the {@link StandardSocketOptions#SO_KEEPALIVE} option. */ SocketChannelConfig setKeepAlive(boolean keepAlive); /** * Gets the {@link StandardSocketOptions#IP_TOS} option. */ int getTrafficClass(); /** * Sets the {@link StandardSocketOptions#IP_TOS} option. */ SocketChannelConfig setTrafficClass(int trafficClass); /** * Gets the {@link StandardSocketOptions#SO_REUSEADDR} option. */ boolean isReuseAddress(); /** * Sets the {@link StandardSocketOptions#SO_REUSEADDR} option. */ SocketChannelConfig setReuseAddress(boolean reuseAddress); /** * Sets the performance preferences as specified in * {@link Socket#setPerformancePreferences(int, int, int)}. */ SocketChannelConfig setPerformancePreferences(int connectionTime, int latency, int bandwidth); @Override SocketChannelConfig setAllowHalfClosure(boolean allowHalfClosure); @Override SocketChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); @Override @Deprecated SocketChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead); @Override SocketChannelConfig setWriteSpinCount(int writeSpinCount); @Override SocketChannelConfig setAllocator(ByteBufAllocator allocator); @Override SocketChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator); @Override SocketChannelConfig setAutoRead(boolean autoRead); @Override SocketChannelConfig setAutoClose(boolean autoClose); @Override SocketChannelConfig setMessageSizeEstimator(MessageSizeEstimator [MASK] ); @Override SocketChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark); } ","estimator " "// Copyright 2018 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.analysis; import static com.google.devtools.build.lib.actions.ActionKeyContext.describeNestedSetFingerprint; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Streams; import com.google.devtools.build.lib.actions.ActionKeyContext; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.CommandLineItem; import com.google.devtools.build.lib.cmdline.LabelConstants; import com.google.devtools.build.lib.collect.nestedset.Depset; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.EventKind; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; import com.google.devtools.build.lib.starlarkbuildapi.RunfilesApi; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; import net.starlark.java.eval.EvalException; import net.starlark.java.eval.Sequence; import net.starlark.java.eval.Starlark; import net.starlark.java.eval.StarlarkSemantics; import net.starlark.java.eval.StarlarkThread; import net.starlark.java.syntax.Location; /** * An object that encapsulates runfiles. Conceptually, the runfiles are a map of paths to files, * forming a symlink tree. * *

In order to reduce memory consumption, this map is not explicitly stored here, but instead as * a combination of three parts: artifacts placed at their output-dir-relative paths, source tree * symlinks and root symlinks (outside of the source tree). */ @Immutable public final class Runfiles implements RunfilesApi { private static class DummyEmptyFilesSupplier implements EmptyFilesSupplier { private static final UUID GUID = UUID.fromString(""36437db7-820b-4386-85b4-f7205a2018ae""); private DummyEmptyFilesSupplier() {} @Override public Iterable getExtraPaths(Set manifestPaths) { return ImmutableList.of(); } @Override public void fingerprint(Fingerprint fp) { fp.addUUID(GUID); } } @SerializationConstant @AutoCodec.VisibleForSerialization static final EmptyFilesSupplier DUMMY_EMPTY_FILES_SUPPLIER = new DummyEmptyFilesSupplier(); // It is important to declare this *after* the DUMMY_SYMLINK_EXPANDER to avoid NPEs public static final Runfiles EMPTY = new Builder().build(); private static final CommandLineItem.ExceptionlessMapFn SYMLINK_ENTRY_MAP_FN = (symlink, args) -> { args.accept(symlink.getPathString()); args.accept(symlink.getArtifact().getExecPathString()); }; private static final CommandLineItem.ExceptionlessMapFn RUNFILES_AND_ABSOLUTE_PATH_MAP_FN = (artifact, args) -> { args.accept(artifact.getRunfilesPathString()); args.accept(artifact.getPath().getPathString()); }; private static final CommandLineItem.ExceptionlessMapFn RUNFILES_AND_EXEC_PATH_MAP_FN = (artifact, args) -> { args.accept(artifact.getRunfilesPathString()); args.accept(artifact.getExecPathString()); }; /** * The directory to put all runfiles under. * *

Using ""foo"" will put runfiles under <target>.runfiles/foo. * *

This is either set to the workspace name, or is empty. */ private final PathFragment suffix; /** * The artifacts that should be present in the runfiles directory. * *

This collection may not include any middlemen. These artifacts will be placed at a location * that corresponds to the output-dir-relative path of each artifact. It's possible for several * artifacts to have the same output-dir-relative path, in which case the last one will win. */ private final NestedSet artifacts; /** * A map of symlinks that should be present in the runfiles directory. In general, the symlink can * be determined from the artifact by using the output-dir-relative path, so this should only be * used for cases where that isn't possible. * *

This may include runfiles symlinks from the root of the runfiles tree. */ private final NestedSet symlinks; /** * A map of symlinks that should be present above the runfiles directory. These are useful for * certain rule types like AppEngine apps which have root level config files outside of the * regular source tree. */ private final NestedSet rootSymlinks; /** * A set of middlemen artifacts. {@link RuleConfiguredTargetBuilder} adds these to the {@link * FilesToRunProvider} of binaries that include this runfiles tree in their runfiles. */ private final NestedSet extraMiddlemen; /** * Interface used for adding empty files to the runfiles at the last minute. Mainly to support * python-related rules adding __init__.py files. */ public interface EmptyFilesSupplier { /** Calculate additional empty files to add based on the existing manifest paths. */ Iterable getExtraPaths(Set manifestPaths); void fingerprint(Fingerprint fingerprint); } /** Generates extra (empty file) inputs. */ private final EmptyFilesSupplier emptyFilesSupplier; /** * Behavior upon finding a conflict between two runfile entries. A conflict means that two * different artifacts have the same runfiles path specified. For example, adding artifact * ""a.foo"" at path ""bar"" when there is already an artifact ""b.foo"" at path ""bar"". The policies * are ordered from least strict to most strict. * *

Note that conflicts are found relatively late, when the manifest file is created, not when * the symlinks are added to runfiles. * *

If no EventHandler is available, all values are treated as IGNORE. */ public enum ConflictPolicy { IGNORE, WARN, ERROR, } /** Policy for this Runfiles tree */ private ConflictPolicy conflictPolicy; /** * If external runfiles should be created under .runfiles/wsname/external/repo as well as * .runfiles/repo. */ private final boolean legacyExternalRunfiles; private Runfiles( PathFragment suffix, NestedSet artifacts, NestedSet symlinks, NestedSet rootSymlinks, NestedSet extraMiddlemen, EmptyFilesSupplier emptyFilesSupplier, ConflictPolicy conflictPolicy, boolean legacyExternalRunfiles) { this.suffix = suffix; this.artifacts = Preconditions.checkNotNull(artifacts); this.symlinks = Preconditions.checkNotNull(symlinks); this.rootSymlinks = Preconditions.checkNotNull(rootSymlinks); this.extraMiddlemen = Preconditions.checkNotNull(extraMiddlemen); this.emptyFilesSupplier = Preconditions.checkNotNull(emptyFilesSupplier); this.conflictPolicy = conflictPolicy; this.legacyExternalRunfiles = legacyExternalRunfiles; } @Override public boolean isImmutable() { return true; // immutable and Starlark-hashable } /** * Returns the runfiles' suffix. */ public PathFragment getSuffix() { return suffix; } public NestedSet getExtraMiddlemen() { return extraMiddlemen; } /** Returns the collection of runfiles as artifacts. */ @Override public Depset /**/ getArtifactsForStarlark() { return Depset.of(Artifact.class, artifacts); } public NestedSet getArtifacts() { return artifacts; } /** Returns the symlinks. */ @Override public Depset /**/ getSymlinksForStarlark() { return Depset.of(SymlinkEntry.class, symlinks); } public NestedSet getSymlinks() { return symlinks; } @Override public Depset /**/ getEmptyFilenamesForStarlark() { return Depset.of(String.class, getEmptyFilenames()); } public NestedSet getEmptyFilenames() { Set manifestKeys = Streams.concat( symlinks.toList().stream().map(SymlinkEntry::getPath), artifacts.toList().stream() .map( artifact -> legacyExternalRunfiles ? artifact.getOutputDirRelativePath(false) : artifact.getRunfilesPath())) .collect(ImmutableSet.toImmutableSet()); Iterable emptyKeys = emptyFilesSupplier.getExtraPaths(manifestKeys); return NestedSetBuilder.stableOrder() .addAll( Streams.stream(emptyKeys) .map(PathFragment::toString) .collect(ImmutableList.toImmutableList())) .build(); } /** * Returns the symlinks as a map from path fragment to artifact. * * @param checker If not null, check for conflicts using this checker. */ Map getSymlinksAsMap(@Nullable ConflictChecker checker) { return entriesToMap(symlinks, checker); } /** * @param eventHandler Used for throwing an error if we have an obscuring runlink. * May be null, in which case obscuring symlinks are silently discarded. * @param location Location for reporter. Ignored if reporter is null. * @param workingManifest Manifest to be checked for obscuring symlinks. * @return map of source file names mapped to their location on disk. */ @VisibleForTesting static Map filterListForObscuringSymlinks( EventHandler eventHandler, Location location, Map workingManifest) { Map newManifest = Maps.newHashMapWithExpectedSize(workingManifest.size()); Set noFurtherObstructions = new HashSet<>(); outer: for (Map.Entry entry : workingManifest.entrySet()) { PathFragment source = entry.getKey(); Artifact symlink = entry.getValue(); // drop nested entries; warn if this changes anything int n = source.segmentCount(); ArrayList parents = new ArrayList<>(n); for (int j = 1; j < n; ++j) { PathFragment prefix = source.subFragment(0, n - j); if (noFurtherObstructions.contains(prefix)) { break; } parents.add(prefix); Artifact ancestor = workingManifest.get(prefix); if (ancestor != null) { // This is an obscuring symlink, so just drop it and move on if there's no reporter. if (eventHandler == null) { continue outer; } PathFragment suffix = source.subFragment(n - j, n); PathFragment viaAncestor = ancestor.getExecPath().getRelative(suffix); PathFragment expected = symlink.getExecPath(); if (!viaAncestor.equals(expected)) { eventHandler.handle( Event.warn( location, ""runfiles symlink "" + source + "" -> "" + expected + "" obscured by "" + prefix + "" -> "" + ancestor.getExecPath())); } continue outer; } } noFurtherObstructions.addAll(parents); newManifest.put(entry.getKey(), entry.getValue()); } return newManifest; } /** * Returns the symlinks as a map from PathFragment to Artifact. * * @param eventHandler Used for throwing an error if we have an obscuring runlink within the * normal source tree entries, or runfile conflicts. May be null, in which case obscuring * symlinks are silently discarded, and conflicts are overwritten. * @param location Location for eventHandler warnings. Ignored if eventHandler is null. * @param repoMappingManifest repository mapping manifest to add as a root symlink. This manifest * has to be added automatically for every executable and is thus not part of the Runfiles * advertised by a configured target. * @return Map path fragment to artifact, of normal source tree entries * and elements that live outside the source tree. Null values represent empty input files. */ public Map getRunfilesInputs( EventHandler eventHandler, Location location, @Nullable Artifact repoMappingManifest) { ConflictChecker checker = new ConflictChecker(conflictPolicy, eventHandler, location); Map manifest = getSymlinksAsMap(checker); // Add artifacts (committed to inclusion on construction of runfiles). for (Artifact artifact : artifacts.toList()) { checker.put(manifest, artifact.getRunfilesPath(), artifact); } manifest = filterListForObscuringSymlinks(eventHandler, location, manifest); // TODO(bazel-team): Create /dev/null-like Artifact to avoid nulls? for (PathFragment extraPath : emptyFilesSupplier.getExtraPaths(manifest.keySet())) { checker.put(manifest, extraPath, null); } // Copy manifest map to another manifest map, prepending the workspace name to every path. // E.g. for workspace ""myworkspace"", the runfile entry ""mylib.so""->""/path/to/mylib.so"" becomes // ""myworkspace/mylib.so""->""/path/to/mylib.so"". ManifestBuilder builder = new ManifestBuilder(suffix, legacyExternalRunfiles); builder.addUnderWorkspace(manifest, checker); // Finally add symlinks relative to the root of the runfiles tree, on top of everything else. // This operation is always checked for conflicts, to match historical behavior. if (conflictPolicy == ConflictPolicy.IGNORE) { checker = new ConflictChecker(ConflictPolicy.WARN, eventHandler, location); } builder.add(getRootSymlinksAsMap(checker), checker); if (repoMappingManifest != null) { checker.put(builder.manifest, PathFragment.create(""_repo_mapping""), repoMappingManifest); } return builder.build(); } /** * Helper class to handle munging the paths of external artifacts. */ @VisibleForTesting static final class ManifestBuilder { // Manifest of paths to artifacts. Path fragments are relative to the .runfiles directory. private final Map manifest; private final PathFragment workspaceName; private final boolean legacyExternalRunfiles; // Whether we saw the local workspace name in the runfiles. If legacyExternalRunfiles is true, // then this is true, as anything under external/ will also have a runfile under the local // workspace. private boolean sawWorkspaceName; ManifestBuilder(PathFragment workspaceName, boolean legacyExternalRunfiles) { this.manifest = new HashMap<>(); this.workspaceName = workspaceName; this.legacyExternalRunfiles = legacyExternalRunfiles; this.sawWorkspaceName = legacyExternalRunfiles; } /** Adds a map under the workspaceName. */ void addUnderWorkspace(Map inputManifest, ConflictChecker checker) { for (Map.Entry entry : inputManifest.entrySet()) { PathFragment path = entry.getKey(); if (isUnderWorkspace(path)) { sawWorkspaceName = true; checker.put(manifest, workspaceName.getRelative(path), entry.getValue()); } else { if (legacyExternalRunfiles) { checker.put(manifest, getLegacyExternalPath(path), entry.getValue()); } // Always add the non-legacy .runfiles/repo/whatever path. checker.put(manifest, getExternalPath(path), entry.getValue()); } } } /** * Adds a map to the root directory. */ public void add(Map inputManifest, ConflictChecker checker) { for (Map.Entry entry : inputManifest.entrySet()) { checker.put(manifest, checkForWorkspace(entry.getKey()), entry.getValue()); } } /** * Returns the manifest, adding the workspaceName directory if it is not already present. */ public Map build() { if (!sawWorkspaceName) { // If we haven't seen it and we have seen other files, add the workspace name directory. // It might not be there if all of the runfiles are from other repos (and then running from // x.runfiles/ws will fail, because ws won't exist). We can't tell Runfiles to create a // directory, so instead this creates a hidden file inside the desired directory. manifest.put(workspaceName.getRelative("".runfile""), null); } return manifest; } private PathFragment getExternalPath(PathFragment path) { return checkForWorkspace(path.subFragment(1)); } private PathFragment getLegacyExternalPath(PathFragment path) { return workspaceName .getRelative(LabelConstants.EXTERNAL_PATH_PREFIX) .getRelative(checkForWorkspace(path.subFragment(1))); } private PathFragment checkForWorkspace(PathFragment path) { sawWorkspaceName = sawWorkspaceName || path.getSegment(0).equals(workspaceName.getPathString()); return path; } private static boolean isUnderWorkspace(PathFragment path) { return !path.startsWith(LabelConstants.EXTERNAL_RUNFILES_PATH_PREFIX); } } /** Returns the root symlinks. */ @Override public Depset /**/ getRootSymlinksForStarlark() { return Depset.of(SymlinkEntry.class, rootSymlinks); } public NestedSet getRootSymlinks() { return rootSymlinks; } /** * Returns the root symlinks as a map from path fragment to artifact. * * @param checker If not null, check for conflicts using this checker. */ public Map getRootSymlinksAsMap(@Nullable ConflictChecker checker) { return entriesToMap(rootSymlinks, checker); } /** * Returns the unified map of path fragments to artifacts, taking both artifacts and symlinks into * account. */ public Map asMapWithoutRootSymlinks() { Map result = entriesToMap(symlinks, null); // If multiple artifacts have the same output-dir-relative path, the last one in the list will // win. That is because the runfiles tree cannot contain the same artifact for different // configurations, because it only uses output-dir-relative paths. for (Artifact artifact : artifacts.toList()) { result.put(artifact.getOutputDirRelativePath(true), artifact); } return result; } /** * Returns the manifest expander specified for this runfiles tree. */ private EmptyFilesSupplier getEmptyFilesProvider() { return emptyFilesSupplier; } /** * Returns the unified map of path fragments to artifacts, taking into account artifacts and * symlinks. The returned set is guaranteed to be a (not necessarily strict) superset of the * actual runfiles tree created at execution time. */ public NestedSet getAllArtifacts() { if (isEmpty()) { return NestedSetBuilder.emptySet(Order.STABLE_ORDER); } NestedSetBuilder allArtifacts = NestedSetBuilder.stableOrder(); allArtifacts .addTransitive(artifacts) .addAll(Iterables.transform(symlinks.toList(), SymlinkEntry::getArtifact)) .addAll(Iterables.transform(rootSymlinks.toList(), SymlinkEntry::getArtifact)); return allArtifacts.build(); } /** * Returns if there are no runfiles. */ public boolean isEmpty() { return artifacts.isEmpty() && symlinks.isEmpty() && rootSymlinks.isEmpty() && extraMiddlemen.isEmpty(); } /** * Flatten a sequence of entries into a single map. * * @param entrySet Sequence of entries to add. * @param checker If not null, check for conflicts with this checker, otherwise silently allow * entries to overwrite previous entries. * @return Map Map of runfile entries. */ private static Map entriesToMap( NestedSet entrySet, @Nullable ConflictChecker checker) { checker = (checker != null) ? checker : ConflictChecker.IGNORE_CHECKER; Map map = new LinkedHashMap<>(); for (SymlinkEntry entry : entrySet.toList()) { checker.put(map, entry.getPath(), entry.getArtifact()); } return map; } /** Returns currently policy for conflicting symlink entries. */ ConflictPolicy getConflictPolicy() { return this.conflictPolicy; } /** Set whether we should warn about conflicting symlink entries. */ @CanIgnoreReturnValue public Runfiles setConflictPolicy(ConflictPolicy conflictPolicy) { this.conflictPolicy = conflictPolicy; return this; } /** * Checks for conflicts between entries in a runfiles tree while putting them in a map. */ public static final class ConflictChecker { /** Prebuilt ConflictChecker with policy set to IGNORE */ static final ConflictChecker IGNORE_CHECKER = new ConflictChecker(ConflictPolicy.IGNORE, null, null); /** Behavior when a conflict is found. */ private final ConflictPolicy policy; /** Used for warning on conflicts. May be null, in which case conflicts are ignored. */ private final EventHandler eventHandler; /** Location for eventHandler warnings. Ignored if eventHandler is null. */ private final Location location; /** Type of event to emit */ private final EventKind eventKind; /** Construct a ConflictChecker for the given reporter with the given behavior */ public ConflictChecker(ConflictPolicy policy, EventHandler eventHandler, Location location) { if (eventHandler == null) { this.policy = ConflictPolicy.IGNORE; // Can't warn even if we wanted to } else { this.policy = policy; } this.eventHandler = eventHandler; this.location = location; this.eventKind = (policy == ConflictPolicy.ERROR) ? EventKind.ERROR : EventKind.WARNING; } /** * Add an entry to a Map of symlinks, optionally reporting conflicts. * * @param map Manifest of runfile entries. * @param path Path fragment to use as key in map. * @param artifact Artifact to store in map. This may be null to indicate an empty file. */ public void put(Map map, PathFragment path, Artifact artifact) { if (artifact != null && artifact.isMiddlemanArtifact() && eventHandler != null) { eventHandler.handle( Event.of( EventKind.ERROR, location, ""Runfiles must not contain middleman artifacts: "" + artifact)); return; } Preconditions.checkArgument( artifact == null || !artifact.isMiddlemanArtifact(), ""%s"", artifact); if (policy != ConflictPolicy.IGNORE && map.containsKey(path)) { // Previous and new entry might have value of null Artifact previous = map.get(path); if (!Objects.equals(previous, artifact)) { String previousStr = (previous == null) ? ""empty file"" : previous.getExecPath().toString(); String artifactStr = (artifact == null) ? ""empty file"" : artifact.getExecPath().toString(); if (!previousStr.equals(artifactStr)) { String message = String.format( ""overwrote runfile %s, was symlink to %s, now symlink to %s"", path.getSafePathString(), previousStr, artifactStr); eventHandler.handle(Event.of(eventKind, location, message)); } } } map.put(path, artifact); } } /** * Builder for Runfiles objects. */ public static final class Builder { /** This is set to the workspace name */ private final PathFragment suffix; /** * This must be COMPILE_ORDER because {@link #asMapWithoutRootSymlinks} overwrites earlier * entries with later ones, so we want a post-order iteration. */ private final NestedSetBuilder artifactsBuilder = NestedSetBuilder.compileOrder(); private final NestedSetBuilder symlinksBuilder = NestedSetBuilder.stableOrder(); private final NestedSetBuilder rootSymlinksBuilder = NestedSetBuilder.stableOrder(); private final NestedSetBuilder extraMiddlemenBuilder = NestedSetBuilder.stableOrder(); private EmptyFilesSupplier emptyFilesSupplier = DUMMY_EMPTY_FILES_SUPPLIER; /** Build the Runfiles object with this policy */ private ConflictPolicy conflictPolicy = ConflictPolicy.IGNORE; private final boolean legacyExternalRunfiles; /** * Only used for Runfiles.EMPTY. */ private Builder() { this.suffix = PathFragment.EMPTY_FRAGMENT; this.legacyExternalRunfiles = false; } /** * Creates a builder with the given suffix. Transitional constructor so that new rules don't * accidentally depend on the legacy repository structure, until that option is removed. * * @param workspace is the string specified in workspace() in the WORKSPACE file. */ public Builder(String workspace) { this(workspace, false); } /** * Creates a builder with the given suffix. * @param workspace is the string specified in workspace() in the WORKSPACE file. * @param legacyExternalRunfiles if the wsname/external/repo symlinks should also be * created. */ public Builder(String workspace, boolean legacyExternalRunfiles) { this(PathFragment.create(workspace), legacyExternalRunfiles); } /** * Creates a builder with the given suffix. * @param suffix is the PathFragment wrapping the string specified in workspace() in the * WORKSPACE file. * @param legacyExternalRunfiles if the wsname/external/repo symlinks should also be * created. */ private Builder(PathFragment suffix, boolean legacyExternalRunfiles) { this.suffix = suffix; this.legacyExternalRunfiles = legacyExternalRunfiles; } /** * Builds a new Runfiles object. */ public Runfiles build() { return new Runfiles( suffix, artifactsBuilder.build(), symlinksBuilder.build(), rootSymlinksBuilder.build(), extraMiddlemenBuilder.build(), emptyFilesSupplier, conflictPolicy, legacyExternalRunfiles); } /** Adds an artifact to the internal collection of artifacts. */ @CanIgnoreReturnValue public Builder addArtifact(Artifact artifact) { Preconditions.checkNotNull(artifact); Preconditions.checkArgument( !artifact.isMiddlemanArtifact(), ""unexpected middleman artifact: %s"", artifact); artifactsBuilder.add(artifact); return this; } /** Adds several artifacts to the internal collection. */ @CanIgnoreReturnValue public Builder addArtifacts(Iterable artifacts) { for (Artifact artifact : artifacts) { addArtifact(artifact); } return this; } /** Adds a nested set to the internal collection. */ @CanIgnoreReturnValue public Builder addTransitiveArtifacts(NestedSet artifacts) { artifactsBuilder.addTransitive(artifacts); return this; } /** * Adds a nested set to the internal collection. * *

The nested set will become wrapped in stable order. Only use this when the set of * artifacts will not have conflicting root relative paths, or the wrong artifact will end up in * the runfiles tree. */ @CanIgnoreReturnValue public Builder addTransitiveArtifactsWrappedInStableOrder(NestedSet artifacts) { NestedSet wrappedArtifacts = NestedSetBuilder.stableOrder().addTransitive(artifacts).build(); artifactsBuilder.addTransitive(wrappedArtifacts); return this; } /** Adds a symlink. */ @CanIgnoreReturnValue public Builder addSymlink(PathFragment link, Artifact target) { symlinksBuilder.add(new SymlinkEntry(link, target)); return this; } /** Adds several symlinks. Neither keys nor values may be null. */ @CanIgnoreReturnValue Builder addSymlinks(Map symlinks) { for (Map.Entry symlink : symlinks.entrySet()) { symlinksBuilder.add(new SymlinkEntry(symlink.getKey(), symlink.getValue())); } return this; } /** Adds several symlinks as a NestedSet. */ @CanIgnoreReturnValue public Builder addSymlinks(NestedSet symlinks) { symlinksBuilder.addTransitive(symlinks); return this; } /** Adds a root symlink. */ @CanIgnoreReturnValue public Builder addRootSymlink(PathFragment link, Artifact target) { rootSymlinksBuilder.add(new SymlinkEntry(link, target)); return this; } /** Adds several root symlinks. Neither keys nor values may be null. */ @CanIgnoreReturnValue public Builder addRootSymlinks(Map symlinks) { for (Map.Entry symlink : symlinks.entrySet()) { rootSymlinksBuilder.add(new SymlinkEntry(symlink.getKey(), symlink.getValue())); } return this; } /** Adds several root symlinks as a NestedSet. */ @CanIgnoreReturnValue public Builder addRootSymlinks(NestedSet symlinks) { rootSymlinksBuilder.addTransitive(symlinks); return this; } /** * Specify a function that can create additional manifest entries based on the input entries, * see {@link EmptyFilesSupplier} for more details. */ @CanIgnoreReturnValue public Builder setEmptyFilesSupplier(EmptyFilesSupplier supplier) { emptyFilesSupplier = Preconditions.checkNotNull(supplier); return this; } /** * Merges runfiles from a given runfiles support. * * @param runfilesSupport the runfiles support to be merged in */ @CanIgnoreReturnValue public Builder merge(@Nullable RunfilesSupport runfilesSupport) { if (runfilesSupport == null) { return this; } merge(runfilesSupport.getRunfiles()); return this; } /** Adds the other {@link Runfiles} object transitively. */ @CanIgnoreReturnValue public Builder merge(Runfiles runfiles) { // Propagate the most strict conflict checking from merged-in runfiles if (runfiles.conflictPolicy.compareTo(conflictPolicy) > 0) { conflictPolicy = runfiles.conflictPolicy; } if (runfiles.isEmpty()) { return this; } // The suffix should be the same within any blaze build, except for the EMPTY runfiles, which // may have an empty suffix, but that is covered above. Preconditions.checkArgument( suffix.equals(runfiles.suffix), ""%s != %s"", suffix, runfiles.suffix); artifactsBuilder.addTransitive(runfiles.getArtifacts()); symlinksBuilder.addTransitive(runfiles.getSymlinks()); rootSymlinksBuilder.addTransitive(runfiles.getRootSymlinks()); extraMiddlemenBuilder.addTransitive(runfiles.getExtraMiddlemen()); if (emptyFilesSupplier == DUMMY_EMPTY_FILES_SUPPLIER) { emptyFilesSupplier = runfiles.getEmptyFilesProvider(); } else { EmptyFilesSupplier otherSupplier = runfiles.getEmptyFilesProvider(); Preconditions.checkState( (otherSupplier == DUMMY_EMPTY_FILES_SUPPLIER) || emptyFilesSupplier.equals(otherSupplier)); } return this; } /** * Adds the runfiles for a particular target and visits the transitive closure of ""srcs"", ""deps"" * and ""data"", collecting all of their respective runfiles. */ @CanIgnoreReturnValue public Builder addRunfiles( RuleContext ruleContext, Function mapping) { Preconditions.checkNotNull(mapping); Preconditions.checkNotNull(ruleContext); addDataDeps(ruleContext); addNonDataDeps(ruleContext, mapping); return this; } /** * Adds the files specified by a mapping from the transitive info collection to the runfiles. * *

Dependencies in {@code srcs} and {@code deps} are considered. */ @CanIgnoreReturnValue public Builder add( RuleContext ruleContext, Function mapping) { Preconditions.checkNotNull(ruleContext); Preconditions.checkNotNull(mapping); for (TransitiveInfoCollection dep : getNonDataDeps(ruleContext)) { Runfiles runfiles = mapping.apply(dep); if (runfiles != null) { merge(runfiles); } } return this; } /** Collects runfiles from data dependencies of a target. */ @CanIgnoreReturnValue public Builder addDataDeps(RuleContext ruleContext) { addTargets( getPrerequisites(ruleContext, ""data""), RunfilesProvider.DATA_RUNFILES, ruleContext.getConfiguration().alwaysIncludeFilesToBuildInData()); return this; } /** Collects runfiles from ""srcs"" and ""deps"" of a target. */ @CanIgnoreReturnValue Builder addNonDataDeps( RuleContext ruleContext, Function mapping) { for (TransitiveInfoCollection target : getNonDataDeps(ruleContext)) { addTargetExceptFileTargets(target, mapping); } return this; } @CanIgnoreReturnValue public Builder addTargets( Iterable targets, Function mapping, boolean alwaysIncludeFilesToBuildInData) { for (TransitiveInfoCollection target : targets) { addTarget(target, mapping, alwaysIncludeFilesToBuildInData); } return this; } @CanIgnoreReturnValue public Builder addTarget( TransitiveInfoCollection target, Function mapping, boolean alwaysIncludeFilesToBuildInData) { return addTargetIncludingFileTargets(target, mapping, alwaysIncludeFilesToBuildInData); } @CanIgnoreReturnValue private Builder addTargetExceptFileTargets( TransitiveInfoCollection target, Function mapping) { Runfiles runfiles = mapping.apply(target); if (runfiles != null) { merge(runfiles); } return this; } private Builder addTargetIncludingFileTargets( TransitiveInfoCollection target, Function mapping, boolean alwaysIncludeFilesToBuildInData) { if (target.getProvider(RunfilesProvider.class) == null && mapping == RunfilesProvider.DATA_RUNFILES) { // RuleConfiguredTarget implements RunfilesProvider, so this will only be called on // FileConfiguredTarget instances. // TODO(bazel-team): This is a terrible hack. We should be able to make this go away // by implementing RunfilesProvider on FileConfiguredTarget. We'd need to be mindful // of the memory use, though, since we have a whole lot of FileConfiguredTarget instances. addTransitiveArtifacts(target.getProvider(FileProvider.class).getFilesToBuild()); return this; } if (alwaysIncludeFilesToBuildInData && mapping == RunfilesProvider.DATA_RUNFILES) { // Ensure that `DefaultInfo.files` of Starlark rules is merged in so that native rules // interoperate well with idiomatic Starlark rules.. // https://bazel.build/extending/rules#runfiles_features_to_avoid // Internal tests fail if the order of filesToBuild is preserved. addTransitiveArtifacts( NestedSetBuilder.stableOrder() .addTransitive(target.getProvider(FileProvider.class).getFilesToBuild()) .build()); } return addTargetExceptFileTargets(target, mapping); } /** * Add extra middlemen artifacts that should be built by reverse dependency binaries. This * method exists solely to support the unfortunate legacy behavior of some rules; new uses * should not be added. */ @CanIgnoreReturnValue public Builder addLegacyExtraMiddleman(Artifact middleman) { Preconditions.checkArgument(middleman.isMiddlemanArtifact(), middleman); extraMiddlemenBuilder.add(middleman); return this; } private static Iterable getNonDataDeps(RuleContext ruleContext) { return Iterables.concat( // TODO(bazel-team): This line shouldn't be here. Removing it requires that no rules have // dependent rules in srcs (except for filegroups and such), but always in deps. // TODO(bazel-team): DONT_CHECK is not optimal here. Rules that use split configs need to // be changed not to call into here. getPrerequisites(ruleContext, ""srcs""), getPrerequisites(ruleContext, ""deps"")); } /** * For the specified attribute ""attributeName"" (which must be of type list(label)), resolves all * the labels into ConfiguredTargets (for the same configuration as this one) and returns them * as a list. * *

If the rule does not have the specified attribute, returns the empty list. */ private static Iterable getPrerequisites( RuleContext ruleContext, String attributeName) { if (ruleContext.getRule().isAttrDefined(attributeName, BuildType.LABEL_LIST)) { return ruleContext.getPrerequisites(attributeName); } else { return Collections.emptyList(); } } } private static void verifyNestedSetDepthLimitHelper( NestedSet [MASK] , String name, int limit) throws EvalException { if ( [MASK] .getApproxDepth() > limit) { throw Starlark.errorf( ""%s depset depth %d exceeds limit (%d)"", name, [MASK] .getApproxDepth(), limit); } } /** * Checks that the depth of a Runfiles object's nested sets (artifacts, symlinks, root symlinks, * extra middlemen) does not exceed Starlark's depset depth limit, as specified by {@code * --nested_set_depth_limit}. * * @param semantics Starlark semantics providing {@code --nested_set_depth_limit} * @return this object, in the fluent style * @throws EvalException if a nested set in the Runfiles object exceeds the depth limit */ @CanIgnoreReturnValue private Runfiles verifyNestedSetDepthLimit(StarlarkSemantics semantics) throws EvalException { int limit = semantics.get(BuildLanguageOptions.NESTED_SET_DEPTH_LIMIT); verifyNestedSetDepthLimitHelper(artifacts, ""artifacts"", limit); verifyNestedSetDepthLimitHelper(symlinks, ""symlinks"", limit); verifyNestedSetDepthLimitHelper(rootSymlinks, ""root symlinks"", limit); verifyNestedSetDepthLimitHelper(extraMiddlemen, ""extra middlemen"", limit); return this; } @Override public Runfiles merge(RunfilesApi other, StarlarkThread thread) throws EvalException { Runfiles o = (Runfiles) other; if (isEmpty()) { // This is not just a memory / performance optimization. The Builder requires a valid suffix, // but the {@code Runfiles.EMPTY} singleton has an invalid one, which must not be used to // construct a Runfiles.Builder. return o; } else if (o.isEmpty()) { return this; } return new Runfiles.Builder(suffix, false) .merge(this) .merge(o) .build() .verifyNestedSetDepthLimit(thread.getSemantics()); } @Override public Runfiles mergeAll(Sequence sequence, StarlarkThread thread) throws EvalException { // The delayed initialization of the Builder is not just a memory / performance optimization. // The Builder requires a valid suffix, but the {@code Runfiles.EMPTY} singleton has an invalid // one, which must not be used to construct a Runfiles.Builder. Builder builder = null; // When merging exactly one non-empty Runfiles object, we want to return that object and avoid a // Builder. This is a memory optimization and provides identical behavior for `x.merge_all([y])` // and `x.merge(y)` in Starlark. Runfiles uniqueNonEmptyMergee = null; if (!this.isEmpty()) { builder = new Builder(suffix, false).merge(this); uniqueNonEmptyMergee = this; } Sequence runfilesSequence = Sequence.cast(sequence, Runfiles.class, ""param""); for (Runfiles runfiles : runfilesSequence) { if (!runfiles.isEmpty()) { if (builder == null) { builder = new Builder(runfiles.suffix, /* legacyExternalRunfiles = */ false); uniqueNonEmptyMergee = runfiles; } else { uniqueNonEmptyMergee = null; } builder.merge(runfiles); } } if (uniqueNonEmptyMergee != null) { return uniqueNonEmptyMergee; } else if (builder != null) { return builder.build().verifyNestedSetDepthLimit(thread.getSemantics()); } else { return EMPTY; } } /** Fingerprint this {@link Runfiles} tree, including the absolute paths of artifacts. */ public void fingerprint( ActionKeyContext actionKeyContext, Fingerprint fp, boolean digestAbsolutePaths) { fp.addInt(conflictPolicy.ordinal()); fp.addBoolean(legacyExternalRunfiles); fp.addPath(suffix); actionKeyContext.addNestedSetToFingerprint(SYMLINK_ENTRY_MAP_FN, fp, symlinks); actionKeyContext.addNestedSetToFingerprint(SYMLINK_ENTRY_MAP_FN, fp, rootSymlinks); actionKeyContext.addNestedSetToFingerprint( digestAbsolutePaths ? RUNFILES_AND_ABSOLUTE_PATH_MAP_FN : RUNFILES_AND_EXEC_PATH_MAP_FN, fp, artifacts); emptyFilesSupplier.fingerprint(fp); // extraMiddlemen does not affect the shape of the runfiles tree described by this instance and // thus does not need to be fingerprinted. } /** Describes the inputs {@link #fingerprint} uses to aid describeKey() descriptions. */ String describeFingerprint(boolean digestAbsolutePaths) { return String.format(""conflictPolicy: %s\n"", conflictPolicy) + String.format(""legacyExternalRunfiles: %s\n"", legacyExternalRunfiles) + String.format(""suffix: %s\n"", suffix) + String.format( ""symlinks: %s\n"", describeNestedSetFingerprint(SYMLINK_ENTRY_MAP_FN, symlinks)) + String.format( ""rootSymlinks: %s\n"", describeNestedSetFingerprint(SYMLINK_ENTRY_MAP_FN, rootSymlinks)) + String.format( ""artifacts: %s\n"", describeNestedSetFingerprint( digestAbsolutePaths ? RUNFILES_AND_ABSOLUTE_PATH_MAP_FN : RUNFILES_AND_EXEC_PATH_MAP_FN, artifacts)) + String.format(""emptyFilesSupplier: %s\n"", emptyFilesSupplier.getClass().getName()); } } ","nestedSet " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ********************************************************************** * Copyright (c) 2002-2015, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Author: Mark Davis ********************************************************************** */ package android.icu.impl; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import android.icu.util.Freezable; /** * A Relation is a set of mappings from keys to values. * Unlike Map, there is not guaranteed to be a single value per key. * The Map-like APIs return collections for values. * @author medavis * @hide Only a subset of ICU is exposed in Android */ public class Relation implements Freezable> { // TODO: add , Map>, but requires API changes private Map> data; Constructor> setCreator; Object[] setComparatorParam; public static Relation of(Map> map, Class setCreator) { return new Relation(map, setCreator); } public static Relation of(Map> map, Class setCreator, Comparator setComparator) { return new Relation(map, setCreator, setComparator); } public Relation(Map> map, Class setCreator) { this(map, setCreator, null); } @SuppressWarnings(""unchecked"") public Relation(Map> map, Class setCreator, Comparator setComparator) { try { setComparatorParam = setComparator == null ? null : new Object[]{setComparator}; if (setComparator == null) { this.setCreator = ((Class>)setCreator).getConstructor(); this.setCreator.newInstance(setComparatorParam); // check to make sure compiles } else { this.setCreator = ((Class>)setCreator).getConstructor(Comparator.class); this.setCreator.newInstance(setComparatorParam); // check to make sure compiles } data = map == null ? new HashMap>() : map; } catch (Exception e) { throw (RuntimeException) new IllegalArgumentException(""Can't create new set"").initCause(e); } } public void clear() { data.clear(); } public boolean containsKey(Object key) { return data.containsKey(key); } public boolean containsValue(Object value) { for (Set values : data.values()) { if (values.contains(value)) { return true; } } return false; } public final Set> entrySet() { return keyValueSet(); } public Set>> keyValuesSet() { return data.entrySet(); } public Set> keyValueSet() { Set> result = new LinkedHashSet>(); for (K key : data.keySet()) { for (V value : data.get(key)) { result.add(new SimpleEntry(key, value)); } } return result; } @Override public boolean equals(Object o) { if (o == null) return false; if (o.getClass() != this.getClass()) return false; return data.equals(((Relation) o).data); } // public V get(Object key) { // Set set = data.get(key); // if (set == null || set.size() == 0) // return null; // return set.iterator().next(); // } public Set getAll(Object key) { return data.get(key); } public Set get(Object key) { return data.get(key); } @Override public int hashCode() { return data.hashCode(); } public boolean isEmpty() { return data.isEmpty(); } public Set keySet() { return data.keySet(); } public V put(K key, V value) { Set set = data.get(key); if (set == null) { data.put(key, set = newSet()); } set.add(value); return value; } public V putAll(K key, Collection values) { Set set = data.get(key); if (set == null) { data.put(key, set = newSet()); } set.addAll(values); return values.size() == 0 ? null : values.iterator().next(); } public V putAll(Collection keys, V value) { V result = null; for (K key : keys) { result = put(key, value); } return result; } private Set newSet() { try { return setCreator.newInstance(setComparatorParam); } catch (Exception e) { throw (RuntimeException) new IllegalArgumentException(""Can't create new set"").initCause(e); } } public void putAll(Map t) { for (Map.Entry entry : t.entrySet()) { put(entry.getKey(), entry.getValue()); } } public void putAll(Relation t) { for (K key : t.keySet()) { for (V value : t.getAll(key)) { put(key, value); } } } public Set removeAll(K key) { try { return data.remove(key); } catch (NullPointerException e) { return null; // data doesn't allow null, eg ConcurrentHashMap } } public boolean remove(K key, V value) { try { Set set = data.get(key); if (set == null) { return false; } boolean result = set.remove(value); if (set.size() == 0) { data.remove(key); } return result; } catch (NullPointerException e) { return false; // data doesn't allow null, eg ConcurrentHashMap } } public int size() { return data.size(); } public Set values() { return values(new LinkedHashSet()); } public > C values(C result) { for (Entry> keyValue : data.entrySet()) { result.addAll(keyValue.getValue()); } return result; } @Override public String toString() { return data.toString(); } static class SimpleEntry implements Entry { K key; V value; public SimpleEntry(K key, V value) { this.key = key; this.value = value; } public SimpleEntry(Entry e) { this.key = e.getKey(); this.value = e.getValue(); } @Override public K getKey() { return key; } @Override public V getValue() { return value; } @Override public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } } public Relation addAllInverted(Relation source) { for (V value : source.data.keySet()) { for (K key : source.data.get(value)) { put(key, value); } } return this; } public Relation addAllInverted(Map source) { for (Map.Entry entry : source.entrySet()) { put(entry.getValue(), entry.getKey()); } return this; } volatile boolean frozen = false; @Override public boolean isFrozen() { return frozen; } @Override public Relation freeze() { if (!frozen) { // does not handle one level down, so we do that on a case-by-case basis for (K key : data.keySet()) { data.put(key, Collections.unmodifiableSet(data.get(key))); } // now do top level data = Collections.unmodifiableMap(data); frozen = true; } return this; } @Override public Relation cloneAsThawed() { // TODO do later throw new UnsupportedOperationException(); } public boolean removeAll(Relation toBeRemoved) { boolean result = false; for (K key : toBeRemoved.keySet()) { try { Set values = toBeRemoved.getAll(key); if (values != null) { result |= removeAll(key, values); } } catch (NullPointerException e) { // data doesn't allow null, eg ConcurrentHashMap } } return result; } public Set removeAll(K... keys) { return removeAll(Arrays.asList(keys)); } public boolean removeAll(K key, Iterable toBeRemoved) { boolean result = false; for (V value : toBeRemoved) { result |= remove(key, value); } return result; } public Set removeAll(Collection toBeRemoved) { Set result = new LinkedHashSet(); for (K key : toBeRemoved) { try { final Set [MASK] = data.remove(key); if ( [MASK] != null) { result.addAll( [MASK] ); } } catch (NullPointerException e) { // data doesn't allow null, eg ConcurrentHashMap } } return result; } } ","removals " "// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skyframe; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.devtools.build.lib.analysis.AspectResolutionHelpers.aspectMatchesConfiguredTarget; import static com.google.devtools.build.lib.skyframe.DependencyResolver.createDefaultToolchainContextKey; import static com.google.devtools.build.lib.skyframe.DependencyResolver.getPrioritizedDetailedExitCode; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.devtools.build.lib.actions.ActionLookupKey; import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; import com.google.devtools.build.lib.analysis.AliasProvider; import com.google.devtools.build.lib.analysis.AspectValue; import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment; import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment.MissingDepException; import com.google.devtools.build.lib.analysis.ConfiguredAspect; import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.ConfiguredTargetValue; import com.google.devtools.build.lib.analysis.DependencyKind; import com.google.devtools.build.lib.analysis.DuplicateException; import com.google.devtools.build.lib.analysis.ExecGroupCollection; import com.google.devtools.build.lib.analysis.ExecGroupCollection.InvalidExecGroupException; import com.google.devtools.build.lib.analysis.IncompatiblePlatformProvider; import com.google.devtools.build.lib.analysis.InconsistentAspectOrderException; import com.google.devtools.build.lib.analysis.ResolvedToolchainContext; import com.google.devtools.build.lib.analysis.TargetAndConfiguration; import com.google.devtools.build.lib.analysis.ToolchainCollection; import com.google.devtools.build.lib.analysis.TransitiveDependencyState; import com.google.devtools.build.lib.analysis.TransitiveDependencyState.PrerequisitePackageFunction; import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; import com.google.devtools.build.lib.analysis.config.ConfigConditions; import com.google.devtools.build.lib.analysis.config.DependencyEvaluationException; import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget; import com.google.devtools.build.lib.analysis.producers.DependencyContext; import com.google.devtools.build.lib.analysis.producers.DependencyContextProducer; import com.google.devtools.build.lib.analysis.producers.UnloadedToolchainContextsInputs; import com.google.devtools.build.lib.analysis.starlark.StarlarkAttributeTransitionProvider; import com.google.devtools.build.lib.bugreport.BugReport; import com.google.devtools.build.lib.causes.Cause; import com.google.devtools.build.lib.causes.LabelCause; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.StoredEventHandler; import com.google.devtools.build.lib.packages.Aspect; import com.google.devtools.build.lib.packages.AspectDefinition; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException; import com.google.devtools.build.lib.packages.NativeAspectClass; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.NoSuchThingException; import com.google.devtools.build.lib.packages.OutputFile; import com.google.devtools.build.lib.packages.Package; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.StarlarkAspectClass; import com.google.devtools.build.lib.packages.StarlarkDefinedAspect; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; import com.google.devtools.build.lib.profiler.memory.CurrentRuleTracker; import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey; import com.google.devtools.build.lib.skyframe.BzlLoadFunction.BzlLoadFailedException; import com.google.devtools.build.lib.skyframe.ConfiguredTargetEvaluationExceptions.UnreportedException; import com.google.devtools.build.lib.skyframe.SkyframeExecutor.BuildViewProvider; import com.google.devtools.build.lib.skyframe.toolchains.ToolchainException; import com.google.devtools.build.lib.skyframe.toolchains.UnloadedToolchainContext; import com.google.devtools.build.lib.util.OrderedSetMultimap; import com.google.devtools.build.skyframe.SkyFunction; import com.google.devtools.build.skyframe.SkyFunction.Environment.SkyKeyComputeState; import com.google.devtools.build.skyframe.SkyFunctionException; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import com.google.devtools.build.skyframe.SkyframeLookupResult; import com.google.devtools.build.skyframe.state.Driver; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import javax.annotation.Nullable; import net.starlark.java.eval.StarlarkSemantics; /** * The Skyframe function that generates aspects. * *

This class, together with {@link ConfiguredTargetFunction} drives the analysis phase. For more * information, see {@link com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory}. * *

{@link AspectFunction} takes a SkyKey containing an {@link AspectKey} [a tuple of (target * label, configurations, aspect class and aspect parameters)], loads an {@link Aspect} from aspect * class and aspect parameters, gets a {@link ConfiguredTarget} for label and configurations, and * then creates a {@link ConfiguredAspect} for a given {@link AspectKey}. * *

See {@link com.google.devtools.build.lib.packages.AspectClass} documentation for an overview * of aspect-related classes * * @see com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory * @see com.google.devtools.build.lib.packages.AspectClass */ final class AspectFunction implements SkyFunction { private final BuildViewProvider buildViewProvider; /** * Indicates whether the set of packages transitively loaded for a given {@link AspectValue} will * be needed later (see {@link * com.google.devtools.build.lib.analysis.ConfiguredObjectValue#getTransitivePackages}). If not, * they are not collected and stored. */ private final boolean storeTransitivePackages; /** * Packages of prerequistes. * *

See {@link ConfiguredTargetFunction#prerequisitePackages} for more details. */ private final PrerequisitePackageFunction prerequisitePackages; AspectFunction( BuildViewProvider buildViewProvider, boolean storeTransitivePackages, PrerequisitePackageFunction prerequisitePackages) { this.buildViewProvider = buildViewProvider; this.storeTransitivePackages = storeTransitivePackages; this.prerequisitePackages = prerequisitePackages; } static class State implements SkyKeyComputeState { @Nullable InitialValues initialValues; final DependencyResolver.State computeDependenciesState; private State( boolean storeTransitivePackages, PrerequisitePackageFunction prerequisitePackages) { this.computeDependenciesState = new DependencyResolver.State(storeTransitivePackages, prerequisitePackages); } } private static class InitialValues { @Nullable private final Aspect aspect; @Nullable private final ConfiguredAspectFactory aspectFactory; private final ConfiguredTarget baseConfiguredTarget; private InitialValues( @Nullable Aspect aspect, @Nullable ConfiguredAspectFactory aspectFactory, ConfiguredTarget baseConfiguredTarget) { this.aspect = aspect; this.aspectFactory = aspectFactory; this.baseConfiguredTarget = baseConfiguredTarget; } } @Nullable @Override public SkyValue compute(SkyKey skyKey, Environment env) throws AspectFunctionException, InterruptedException { AspectKey key = (AspectKey) skyKey.argument(); State state = env.getState(() -> new State(storeTransitivePackages, prerequisitePackages)); DependencyResolver.State computeDependenciesState = state.computeDependenciesState; if (state.initialValues == null) { InitialValues initialValues = getInitialValues(computeDependenciesState, key, env); if (initialValues == null) { return null; } state.initialValues = initialValues; } Aspect aspect = state.initialValues.aspect; ConfiguredAspectFactory aspectFactory = state.initialValues.aspectFactory; ConfiguredTarget associatedTarget = state.initialValues.baseConfiguredTarget; TargetAndConfiguration targetAndConfiguration = computeDependenciesState.targetAndConfiguration; Target target = targetAndConfiguration.getTarget(); BuildConfigurationValue configuration = targetAndConfiguration.getConfiguration(); // If the target is incompatible, then there's not much to do. The intent here is to create an // AspectValue that doesn't trigger any of the associated target's dependencies to be evaluated // against this aspect. if (associatedTarget.get(IncompatiblePlatformProvider.PROVIDER) != null || // Similarly, aspects that propagate into post-NoConfigTransition targets can't access // most flags or dependencies and are likely to be unsound. So make aspects propagating to // these configurations no-ops. (configuration != null && configuration.getOptions().hasNoConfig())) { return AspectValue.create( key, aspect, ConfiguredAspect.forNonapplicableTarget(), computeDependenciesState.transitivePackages()); } if (AliasProvider.isAlias(associatedTarget)) { return createAliasAspect( env, targetAndConfiguration, aspect, key, associatedTarget, computeDependenciesState.transitiveState); } // If we get here, label should match original label, and therefore the target we looked up // above indeed corresponds to associatedTarget.getLabel(). Preconditions.checkState( associatedTarget.getOriginalLabel().equals(associatedTarget.getLabel()), ""Non-alias %s should have matching label but found %s"", associatedTarget.getOriginalLabel(), associatedTarget.getLabel()); // If the incompatible flag is set, the top-level aspect should not be applied on top-level // targets whose rules do not advertise the aspect's required providers. The aspect should not // also propagate to these targets dependencies. StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env); if (starlarkSemantics == null) { return null; } boolean checkRuleAdvertisedProviders = starlarkSemantics.getBool( BuildLanguageOptions.INCOMPATIBLE_TOP_LEVEL_ASPECTS_REQUIRE_PROVIDERS); if (checkRuleAdvertisedProviders) { if (target instanceof Rule) { if (!aspect .getDefinition() .getRequiredProviders() .isSatisfiedBy(((Rule) target).getRuleClassObject().getAdvertisedProviders())) { return AspectValue.create( key, aspect, ConfiguredAspect.forNonapplicableTarget(), computeDependenciesState.transitivePackages()); } } } ImmutableList topologicalAspectPath; if (key.getBaseKeys().isEmpty()) { topologicalAspectPath = ImmutableList.of(aspect); } else { LinkedHashSet orderedKeys = new LinkedHashSet<>(); collectAspectKeysInTopologicalOrder(key.getBaseKeys(), orderedKeys); SkyframeLookupResult aspectValues = env.getValuesAndExceptions(orderedKeys); if (env.valuesMissing()) { return null; } ImmutableList.Builder [MASK] = ImmutableList.builderWithExpectedSize(orderedKeys.size() + 1); for (AspectKey aspectKey : orderedKeys) { AspectValue aspectValue = (AspectValue) aspectValues.get(aspectKey); if (aspectValue == null) { BugReport.logUnexpected( ""aspectValue for: '%s' was missing, this should never happen"", aspectKey); return null; } [MASK] .add(aspectValue.getAspect()); } topologicalAspectPath = [MASK] .add(aspect).build(); List directlyRequiredAspects = Lists.transform(key.getBaseKeys(), k -> ((AspectValue) aspectValues.get(k))); try { associatedTarget = MergedConfiguredTarget.of(associatedTarget, directlyRequiredAspects); } catch (DuplicateException e) { env.getListener().handle(Event.error(target.getLocation(), e.getMessage())); throw new AspectFunctionException( new AspectCreationException(e.getMessage(), target.getLabel(), configuration)); } } try { var dependencyContext = getDependencyContext(computeDependenciesState, key, aspect, env); if (dependencyContext == null) { return null; } Optional starlarkExecTransition; try { starlarkExecTransition = DependencyResolver.loadStarlarkExecTransition(targetAndConfiguration, env); if (starlarkExecTransition == null) { return null; // Need Skyframe deps. } } catch (UnreportedException e) { throw new AspectCreationException(e.getMessage(), key.getLabel(), configuration); } OrderedSetMultimap depValueMap = DependencyResolver.computeDependencies( computeDependenciesState, ConfiguredTargetKey.fromConfiguredTarget(associatedTarget), topologicalAspectPath, buildViewProvider.getSkyframeBuildView().getStarlarkTransitionCache(), starlarkExecTransition.orElse(null), env, env.getListener()); if (!computeDependenciesState.transitiveRootCauses().isEmpty()) { NestedSet causes = computeDependenciesState.transitiveRootCauses().build(); throw new AspectFunctionException( new AspectCreationException( ""Loading failed"", causes, getPrioritizedDetailedExitCode(causes))); } if (depValueMap == null) { return null; } // Load the requested toolchains into the ToolchainContext, now that we have dependencies. ToolchainCollection unloadedToolchainContexts = dependencyContext.unloadedToolchainContexts(); ToolchainCollection toolchainContexts = null; if (unloadedToolchainContexts != null) { String targetDescription = ""aspect "" + aspect.getDescriptor().getDescription() + "" applied to "" + target; ToolchainCollection.Builder contextsBuilder = ToolchainCollection.builder(); for (Map.Entry unloadedContext : unloadedToolchainContexts.getContextMap().entrySet()) { ImmutableSet toolchainDependencies = ImmutableSet.copyOf( depValueMap.get(DependencyKind.forExecGroup(unloadedContext.getKey()))); contextsBuilder.addContext( unloadedContext.getKey(), ResolvedToolchainContext.load( unloadedContext.getValue(), targetDescription, toolchainDependencies)); } toolchainContexts = contextsBuilder.build(); } return createAspect( env, key, topologicalAspectPath, aspect, aspectFactory, target, associatedTarget, configuration, dependencyContext.configConditions(), toolchainContexts, computeDependenciesState.execGroupCollectionBuilder, depValueMap, computeDependenciesState.transitiveState); } catch (DependencyEvaluationException e) { // TODO(bazel-team): consolidate all env.getListener().handle() calls in this method, like in // ConfiguredTargetFunction. This encourages clear, consistent user messages (ideally without // the programmer having to think about it). if (!e.depReportedOwnError()) { env.getListener().handle(Event.error(e.getLocation(), e.getMessage())); } if (e.getCause() instanceof ConfiguredValueCreationException) { ConfiguredValueCreationException cause = (ConfiguredValueCreationException) e.getCause(); throw new AspectFunctionException( new AspectCreationException( cause.getMessage(), cause.getRootCauses(), cause.getDetailedExitCode())); } // Cast to InconsistentAspectOrderException as a consistency check. If you add any // DependencyEvaluationException constructors, you may need to change this code, too. InconsistentAspectOrderException cause = (InconsistentAspectOrderException) e.getCause(); env.getListener().handle(Event.error(cause.getLocation(), cause.getMessage())); throw new AspectFunctionException( new AspectCreationException(cause.getMessage(), key.getLabel(), configuration)); } catch (AspectCreationException e) { throw new AspectFunctionException(e); } catch (ConfiguredValueCreationException e) { throw new AspectFunctionException(e); } catch (ToolchainException e) { throw new AspectFunctionException( new AspectCreationException( e.getMessage(), new LabelCause(key.getLabel(), e.getDetailedExitCode()))); } } /** Populates {@code state.execGroupCollection} as a side effect. */ @Nullable // Null if a Skyframe restart is needed. private DependencyContext getDependencyContext( DependencyResolver.State state, AspectKey key, Aspect aspect, Environment env) throws InterruptedException, ConfiguredValueCreationException, ToolchainException { if (state.dependencyContext != null) { return state.dependencyContext; } if (state.dependencyContextProducer == null) { TargetAndConfiguration targetAndConfiguration = state.targetAndConfiguration; UnloadedToolchainContextsInputs unloadedToolchainContextsInputs = getUnloadedToolchainContextsInputs( aspect.getDefinition(), key.getConfigurationKey(), targetAndConfiguration.getConfiguration()); state.execGroupCollectionBuilder = unloadedToolchainContextsInputs; state.dependencyContextProducer = new Driver( new DependencyContextProducer( unloadedToolchainContextsInputs, targetAndConfiguration, key.getConfigurationKey(), state.transitiveState, (DependencyContextProducer.ResultSink) state)); } if (state.dependencyContextProducer.drive(env)) { state.dependencyContextProducer = null; } // During error bubbling, the state machine might not be done, but still emit an error. var error = state.dependencyContextError; if (error != null) { switch (error.kind()) { case TOOLCHAIN: throw error.toolchain(); case CONFIGURED_VALUE_CREATION: throw error.configuredValueCreation(); case INCOMPATIBLE_TARGET: throw new IllegalStateException(""Unexpected error: "" + error.incompatibleTarget()); case VALIDATION: throw new IllegalStateException(""Unexpected error: "" + error.validation()); } throw new IllegalStateException(""unreachable""); } return state.dependencyContext; // Null if not yet done. } static BzlLoadValue.Key bzlLoadKeyForStarlarkAspect(StarlarkAspectClass starlarkAspectClass) { Label extensionLabel = starlarkAspectClass.getExtensionLabel(); return StarlarkBuiltinsValue.isBuiltinsRepo(extensionLabel.getRepository()) ? BzlLoadValue.keyForBuiltins(extensionLabel) : BzlLoadValue.keyForBuild(extensionLabel); } @Nullable private static InitialValues getInitialValues( DependencyResolver.State state, AspectKey key, Environment env) throws AspectFunctionException, InterruptedException { ActionLookupKey configuredTargetLookupKey = key.getBaseConfiguredTargetKey(); PackageIdentifier basePackageKey = key.getBaseConfiguredTargetKey().getLabel().getPackageIdentifier(); var initialKeys = ImmutableSet.builder().add(configuredTargetLookupKey).add(basePackageKey); BuildConfigurationKey configurationKey = key.getConfigurationKey(); if (configurationKey != null) { initialKeys.add(configurationKey); } StarlarkAspectClass starlarkAspectClass; BzlLoadValue.Key bzlLoadKey; if (key.getAspectClass() instanceof NativeAspectClass) { starlarkAspectClass = null; bzlLoadKey = null; } else { Preconditions.checkState( key.getAspectClass() instanceof StarlarkAspectClass, ""Unknown aspect class: %s"", key); starlarkAspectClass = (StarlarkAspectClass) key.getAspectClass(); initialKeys.add(bzlLoadKey = bzlLoadKeyForStarlarkAspect(starlarkAspectClass)); } SkyframeLookupResult initialValues = env.getValuesAndExceptions(initialKeys.build()); if (env.valuesMissing()) { return null; } ConfiguredTarget baseConfiguredTarget; try { var baseConfiguredTargetValue = (ConfiguredTargetValue) initialValues.getOrThrow( configuredTargetLookupKey, ConfiguredValueCreationException.class); if (baseConfiguredTargetValue == null) { BugReport.logUnexpected( ""Unexpected exception with %s and AspectKey %s"", key.getBaseConfiguredTargetKey(), key); return null; } baseConfiguredTarget = baseConfiguredTargetValue.getConfiguredTarget(); } catch (ConfiguredValueCreationException e) { throw new AspectFunctionException( new AspectCreationException(e.getMessage(), e.getRootCauses(), e.getDetailedExitCode())); } Preconditions.checkState( Objects.equals(key.getConfigurationKey(), baseConfiguredTarget.getConfigurationKey()), ""Aspect not in same configuration as base configured target: %s, %s"", key, baseConfiguredTarget); // Keep this in sync with the same code in ConfiguredTargetFunction. Package basePackage = ((PackageValue) initialValues.get(basePackageKey)).getPackage(); if (basePackage.containsErrors()) { throw new AspectFunctionException( new BuildFileContainsErrorsException(key.getLabel().getPackageIdentifier())); } Target target; try { target = basePackage.getTarget(baseConfiguredTarget.getOriginalLabel().getName()); } catch (NoSuchTargetException e) { throw new IllegalStateException(""Name already verified"", e); } BuildConfigurationValue configuration = configurationKey == null ? null : (BuildConfigurationValue) initialValues.get(configurationKey); state.targetAndConfiguration = new TargetAndConfiguration(target, configuration); ConfiguredAspectFactory aspectFactory; Aspect aspect; if (bzlLoadKey == null) { NativeAspectClass nativeAspectClass = (NativeAspectClass) key.getAspectClass(); aspectFactory = (ConfiguredAspectFactory) nativeAspectClass; aspect = Aspect.forNative(nativeAspectClass, key.getParameters()); } else { StarlarkDefinedAspect starlarkAspect; try { BzlLoadValue bzlLoadvalue; try { bzlLoadvalue = (BzlLoadValue) initialValues.getOrThrow(bzlLoadKey, BzlLoadFailedException.class); if (bzlLoadvalue == null) { BugReport.logUnexpected( ""Unexpected exception with %s and AspectKey %s"", bzlLoadKey, key); return null; } } catch (BzlLoadFailedException e) { throw new AspectCreationException( e.getMessage(), starlarkAspectClass.getExtensionLabel(), e.getDetailedExitCode()); } starlarkAspect = loadAspectFromBzl(starlarkAspectClass, bzlLoadvalue); } catch (AspectCreationException e) { env.getListener().handle(Event.error(e.getMessage())); throw new AspectFunctionException(e); } aspectFactory = new StarlarkAspectFactory(starlarkAspect); aspect = Aspect.forStarlark( starlarkAspect.getAspectClass(), starlarkAspect.getDefinition(key.getParameters()), key.getParameters()); } return new InitialValues(aspect, aspectFactory, baseConfiguredTarget); } /** * Loads a Starlark-defined aspect from an extension file. * * @throws AspectCreationException if the value loaded is not a {@link StarlarkDefinedAspect} */ static StarlarkDefinedAspect loadAspectFromBzl( StarlarkAspectClass starlarkAspectClass, BzlLoadValue bzlLoadValue) throws AspectCreationException { Label extensionLabel = starlarkAspectClass.getExtensionLabel(); String starlarkValueName = starlarkAspectClass.getExportedName(); Object starlarkValue = bzlLoadValue.getModule().getGlobal(starlarkValueName); if (!(starlarkValue instanceof StarlarkDefinedAspect)) { throw new AspectCreationException( String.format( starlarkValue == null ? ""%s is not exported from %s"" : ""%s from %s is not an aspect"", starlarkValueName, extensionLabel), extensionLabel); } return (StarlarkDefinedAspect) starlarkValue; } @Nullable private static UnloadedToolchainContextsInputs getUnloadedToolchainContextsInputs( AspectDefinition aspectDefinition, @Nullable BuildConfigurationKey configurationKey, @Nullable BuildConfigurationValue configuration) { if (configuration == null) { // Configuration can be null in the case of aspects applied to input files. In this case, // there are no toolchains being used. return UnloadedToolchainContextsInputs.empty(); } boolean useAutoExecGroups = shouldUseAutoExecGroups(aspectDefinition, configuration); var processedExecGroups = ExecGroupCollection.process( aspectDefinition.execGroups(), aspectDefinition.execCompatibleWith(), aspectDefinition.getToolchainTypes(), useAutoExecGroups); // Note: `configuration.getOptions().hasNoConfig()` is handled early in #compute. return UnloadedToolchainContextsInputs.create( processedExecGroups, createDefaultToolchainContextKey( configurationKey, aspectDefinition.execCompatibleWith(), /* debugTarget= */ false, /* useAutoExecGroups= */ useAutoExecGroups, aspectDefinition.getToolchainTypes(), /* parentExecutionPlatformLabel= */ null)); } private static boolean shouldUseAutoExecGroups( AspectDefinition aspectDefinition, BuildConfigurationValue configuration) { ImmutableMap aspectAttributes = aspectDefinition.getAttributes(); if (aspectAttributes.containsKey(""$use_auto_exec_groups"")) { return (boolean) aspectAttributes.get(""$use_auto_exec_groups"").getDefaultValueUnchecked(); } return configuration.useAutoExecGroups(); } /** * Collects {@link AspectKey} dependencies by performing a postorder traversal over {@link * AspectKey#getBaseKeys}. * *

The resulting set of {@code orderedKeys} is topologically ordered: each aspect key appears * after all of its dependencies. */ private static void collectAspectKeysInTopologicalOrder( List baseKeys, LinkedHashSet orderedKeys) { for (AspectKey key : baseKeys) { if (!orderedKeys.contains(key)) { collectAspectKeysInTopologicalOrder(key.getBaseKeys(), orderedKeys); orderedKeys.add(key); } } } /** * Computes the given aspectKey of an alias-like target, by depending on the corresponding key of * the next target in the alias chain (if there are more), or the ""real"" configured target. */ @Nullable private AspectValue createAliasAspect( Environment env, TargetAndConfiguration targetAndConfiguration, Aspect aspect, AspectKey originalKey, ConfiguredTarget baseConfiguredTarget, TransitiveDependencyState transitiveState) throws InterruptedException { ImmutableList

 * 
 *
 * JadxArgs args = new JadxArgs();
 * args.getInputFiles().add(new File(""test.apk""));
 * args.setOutDir(new File(""jadx-test-output""));
 * try (JadxDecompiler jadx = new JadxDecompiler(args)) {
 *    jadx.load();
 *    jadx.save();
 * }
 * 
 * 
*

* Instead of 'save()' you can iterate over decompiled classes: * *

 * 
 *
 *  for(JavaClass cls : jadx.getClasses()) {
 *      System.out.println(cls.getCode());
 *  }
 * 
 * 
*/ public final class JadxDecompiler implements Closeable { private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class); private final JadxArgs args; private final JadxPluginManager pluginManager = new JadxPluginManager(this); private final List loadedInputs = new ArrayList<>(); private RootNode root; private List classes; private List resources; private BinaryXMLParser binaryXmlParser; private ProtoXMLParser protoXmlParser; private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(); private final JadxEventsImpl events = new JadxEventsImpl(); private final List customCodeLoaders = new ArrayList<>(); private final Map> customPasses = new HashMap<>(); public JadxDecompiler() { this(new JadxArgs()); } public JadxDecompiler(JadxArgs args) { this.args = args; } public void load() { reset(); JadxArgsValidator.validate(this); LOG.info(""loading ...""); loadPlugins(); loadInputFiles(); root = new RootNode(args); root.init(); root.setDecompilerRef(this); root.mergePasses(customPasses); root.loadClasses(loadedInputs); root.initClassPath(); root.loadResources(getResources()); root.runPreDecompileStage(); root.initPasses(); loadFinished(); } public void reloadPasses() { LOG.info(""reloading (passes only) ...""); customPasses.clear(); root.resetPasses(); events.reset(); loadPlugins(); root.mergePasses(customPasses); root.restartVisitors(); root.initPasses(); loadFinished(); } private void loadInputFiles() { loadedInputs.clear(); List inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath); List [MASK] = FileUtils.expandDirs(inputPaths); long start = System.currentTimeMillis(); for (JadxCodeInput codeLoader : pluginManager.getCodeInputs()) { ICodeLoader loader = codeLoader.loadFiles( [MASK] ); if (loader != null && !loader.isEmpty()) { loadedInputs.add(loader); } } loadedInputs.addAll(customCodeLoaders); if (LOG.isDebugEnabled()) { LOG.debug(""Loaded using {} inputs plugin in {} ms"", loadedInputs.size(), System.currentTimeMillis() - start); } } private void reset() { root = null; classes = null; resources = null; binaryXmlParser = null; protoXmlParser = null; events.reset(); } @Override public void close() { reset(); closeInputs(); args.close(); } private void closeInputs() { loadedInputs.forEach(load -> { try { load.close(); } catch (Exception e) { LOG.error(""Failed to close input"", e); } }); loadedInputs.clear(); } private void loadPlugins() { pluginManager.providesSuggestion(""java-input"", args.isUseDxInput() ? ""java-convert"" : ""java-input""); pluginManager.load(args.getPluginLoader()); if (LOG.isDebugEnabled()) { LOG.debug(""Resolved plugins: {}"", pluginManager.getResolvedPluginContexts()); } pluginManager.initResolved(); if (LOG.isDebugEnabled()) { List passes = customPasses.values().stream().flatMap(Collection::stream) .map(p -> p.getInfo().getName()).collect(Collectors.toList()); LOG.debug(""Loaded custom passes: {} {}"", passes.size(), passes); } } private void loadFinished() { LOG.debug(""Load finished""); List list = customPasses.get(JadxAfterLoadPass.TYPE); if (list != null) { for (JadxPass pass : list) { ((JadxAfterLoadPass) pass).init(this); } } } @SuppressWarnings(""unused"") public void registerPlugin(JadxPlugin plugin) { pluginManager.register(plugin); } public static String getVersion() { return Jadx.getVersion(); } public void save() { save(!args.isSkipSources(), !args.isSkipResources()); } public interface ProgressListener { void progress(long done, long total); } @SuppressWarnings(""BusyWait"") public void save(int intervalInMillis, ProgressListener listener) { ThreadPoolExecutor ex = (ThreadPoolExecutor) getSaveExecutor(); ex.shutdown(); try { long total = ex.getTaskCount(); while (ex.isTerminating()) { long done = ex.getCompletedTaskCount(); listener.progress(done, total); Thread.sleep(intervalInMillis); } } catch (InterruptedException e) { LOG.error(""Save interrupted"", e); Thread.currentThread().interrupt(); } } public void saveSources() { save(true, false); } public void saveResources() { save(false, true); } @SuppressWarnings(""ResultOfMethodCallIgnored"") private void save(boolean saveSources, boolean saveResources) { ExecutorService ex = getSaveExecutor(saveSources, saveResources); ex.shutdown(); try { ex.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { LOG.error(""Save interrupted"", e); Thread.currentThread().interrupt(); } } public ExecutorService getSaveExecutor() { return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources()); } public List getSaveTasks() { return getSaveTasks(!args.isSkipSources(), !args.isSkipResources()); } private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) { int threadsCount = args.getThreadsCount(); LOG.debug(""processing threads count: {}"", threadsCount); LOG.info(""processing ...""); ExecutorService executor = Executors.newFixedThreadPool(threadsCount); List tasks = getSaveTasks(saveSources, saveResources); tasks.forEach(executor::execute); return executor; } private List getSaveTasks(boolean saveSources, boolean saveResources) { if (root == null) { throw new JadxRuntimeException(""No loaded files""); } File sourcesOutDir; File resOutDir; List tasks = new ArrayList<>(); TaskBarrier barrier = new TaskBarrier(); if (args.isExportAsGradleProject()) { ExportGradleTask gradleExportTask = new ExportGradleTask(resources, root, args.getOutDir(), barrier); gradleExportTask.init(); sourcesOutDir = gradleExportTask.getSrcOutDir(); resOutDir = gradleExportTask.getResOutDir(); tasks.add(gradleExportTask); } else { sourcesOutDir = args.getOutDirSrc(); resOutDir = args.getOutDirRes(); } int taskCount = 0; // save resources first because decompilation can hang or fail if (saveResources) { taskCount = appendResourcesSaveTasks(tasks, resOutDir, barrier); } if (saveSources) { taskCount += appendSourcesSave(tasks, sourcesOutDir, barrier); } barrier.setUpBarrier(taskCount); return tasks; } private int appendResourcesSaveTasks(List tasks, File outDir, TaskBarrier barrier) { int numResourceTasks = 0; if (args.isSkipFilesSave()) { return 0; } // process AndroidManifest.xml first to load complete resource ids table for (ResourceFile resourceFile : getResources()) { if (resourceFile.getType() == ResourceType.MANIFEST) { new ResourcesSaver(outDir, resourceFile).run(); break; } } Set inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet()); for (ResourceFile resourceFile : getResources()) { ResourceType resType = resourceFile.getType(); if (resType == ResourceType.MANIFEST) { // already processed continue; } if (resType != ResourceType.ARSC && inputFileNames.contains(resourceFile.getOriginalName())) { // ignore resource made from input file continue; } tasks.add(new ResourcesSaver(outDir, resourceFile, barrier)); numResourceTasks++; } return numResourceTasks; } private int appendSourcesSave(List tasks, File outDir, TaskBarrier barrier) { int numSourceTasks = 0; Predicate classFilter = args.getClassFilter(); List classes = getClasses(); List processQueue = new ArrayList<>(classes.size()); for (JavaClass cls : classes) { ClassNode clsNode = cls.getClassNode(); if (clsNode.contains(AFlag.DONT_GENERATE)) { continue; } if (classFilter != null && !classFilter.test(clsNode.getClassInfo().getFullName())) { if (!args.isIncludeDependencies()) { clsNode.add(AFlag.DONT_GENERATE); } continue; } processQueue.add(cls); } List> batches; try { batches = decompileScheduler.buildBatches(processQueue); } catch (Exception e) { throw new JadxRuntimeException(""Decompilation batches build failed"", e); } for (List decompileBatch : batches) { tasks.add(() -> { try { for (JavaClass cls : decompileBatch) { try { ClassNode clsNode = cls.getClassNode(); ICodeInfo code = clsNode.getCode(); SaveCode.save(outDir, clsNode, code); } catch (Exception e) { LOG.error(""Error saving class: {}"", cls, e); } } } finally { if (barrier != null) { barrier.finishTask(); } } }); numSourceTasks++; } return numSourceTasks; } public List getClasses() { if (root == null) { return Collections.emptyList(); } if (classes == null) { List classNodeList = root.getClasses(); List clsList = new ArrayList<>(classNodeList.size()); for (ClassNode classNode : classNodeList) { if (classNode.contains(AFlag.DONT_GENERATE)) { continue; } if (!classNode.getClassInfo().isInner()) { clsList.add(convertClassNode(classNode)); } } classes = Collections.unmodifiableList(clsList); } return classes; } public List getClassesWithInners() { return Utils.collectionMap(root.getClasses(), this::convertClassNode); } public synchronized List getResources() { if (resources == null) { if (root == null) { return Collections.emptyList(); } resources = new ResourcesLoader(this).load(); } return resources; } public List getPackages() { return Utils.collectionMap(root.getPackages(), this::convertPackageNode); } public int getErrorsCount() { if (root == null) { return 0; } return root.getErrorsCounter().getErrorCount(); } public int getWarnsCount() { if (root == null) { return 0; } return root.getErrorsCounter().getWarnsCount(); } public void printErrorsReport() { if (root == null) { return; } root.getClsp().printMissingClasses(); root.getErrorsCounter().printReport(); } /** * Internal API. Not Stable! */ @ApiStatus.Internal public RootNode getRoot() { return root; } synchronized BinaryXMLParser getBinaryXmlParser() { if (binaryXmlParser == null) { binaryXmlParser = new BinaryXMLParser(root); } return binaryXmlParser; } synchronized ProtoXMLParser getProtoXmlParser() { if (protoXmlParser == null) { protoXmlParser = new ProtoXMLParser(root); } return protoXmlParser; } /** * Get JavaClass by ClassNode without loading and decompilation */ @ApiStatus.Internal synchronized JavaClass convertClassNode(ClassNode cls) { JavaClass javaClass = cls.getJavaNode(); if (javaClass == null) { javaClass = cls.isInner() ? new JavaClass(cls, convertClassNode(cls.getParentClass())) : new JavaClass(cls, this); cls.setJavaNode(javaClass); } return javaClass; } @ApiStatus.Internal synchronized JavaField convertFieldNode(FieldNode fld) { JavaField javaField = fld.getJavaNode(); if (javaField == null) { JavaClass parentCls = convertClassNode(fld.getParentClass()); javaField = new JavaField(parentCls, fld); fld.setJavaNode(javaField); } return javaField; } @ApiStatus.Internal synchronized JavaMethod convertMethodNode(MethodNode mth) { JavaMethod javaMethod = mth.getJavaNode(); if (javaMethod == null) { javaMethod = new JavaMethod(convertClassNode(mth.getParentClass()), mth); mth.setJavaNode(javaMethod); } return javaMethod; } @ApiStatus.Internal synchronized JavaPackage convertPackageNode(PackageNode pkg) { JavaPackage foundPkg = pkg.getJavaNode(); if (foundPkg != null) { return foundPkg; } List clsList = Utils.collectionMap(pkg.getClasses(), this::convertClassNode); int subPkgsCount = pkg.getSubPackages().size(); List subPkgs = subPkgsCount == 0 ? Collections.emptyList() : new ArrayList<>(subPkgsCount); JavaPackage javaPkg = new JavaPackage(pkg, clsList, subPkgs); if (subPkgsCount != 0) { // add subpackages after parent to avoid endless recursion for (PackageNode subPackage : pkg.getSubPackages()) { subPkgs.add(convertPackageNode(subPackage)); } } pkg.setJavaNode(javaPkg); return javaPkg; } @Nullable public JavaClass searchJavaClassByOrigFullName(String fullName) { return getRoot().getClasses().stream() .filter(cls -> cls.getClassInfo().getFullName().equals(fullName)) .findFirst() .map(this::convertClassNode) .orElse(null); } @Nullable public ClassNode searchClassNodeByOrigFullName(String fullName) { return getRoot().getClasses().stream() .filter(cls -> cls.getClassInfo().getFullName().equals(fullName)) .findFirst() .orElse(null); } // returns parent if class contains DONT_GENERATE flag. @Nullable public JavaClass searchJavaClassOrItsParentByOrigFullName(String fullName) { ClassNode node = getRoot().getClasses().stream() .filter(cls -> cls.getClassInfo().getFullName().equals(fullName)) .findFirst() .orElse(null); if (node != null) { if (node.contains(AFlag.DONT_GENERATE)) { return convertClassNode(node.getTopParentClass()); } else { return convertClassNode(node); } } return null; } @Nullable public JavaClass searchJavaClassByAliasFullName(String fullName) { return getRoot().getClasses().stream() .filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName)) .findFirst() .map(this::convertClassNode) .orElse(null); } @Nullable public JavaNode getJavaNodeByRef(ICodeNodeRef ann) { return getJavaNodeByCodeAnnotation(null, ann); } @Nullable public JavaNode getJavaNodeByCodeAnnotation(@Nullable ICodeInfo codeInfo, @Nullable ICodeAnnotation ann) { if (ann == null) { return null; } switch (ann.getAnnType()) { case CLASS: return convertClassNode((ClassNode) ann); case METHOD: return convertMethodNode((MethodNode) ann); case FIELD: return convertFieldNode((FieldNode) ann); case DECLARATION: return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode()); case VAR: return resolveVarNode((VarNode) ann); case VAR_REF: return resolveVarRef(codeInfo, (VarRef) ann); case OFFSET: // offset annotation don't have java node object return null; default: throw new JadxRuntimeException(""Unknown annotation type: "" + ann.getAnnType() + "", class: "" + ann.getClass()); } } private JavaVariable resolveVarNode(VarNode varNode) { JavaMethod javaNode = convertMethodNode(varNode.getMth()); return new JavaVariable(javaNode, varNode); } @Nullable private JavaVariable resolveVarRef(ICodeInfo codeInfo, VarRef varRef) { if (codeInfo == null) { throw new JadxRuntimeException(""Missing code info for resolve VarRef: "" + varRef); } ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos()); if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) { ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode(); if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) { return resolveVarNode((VarNode) nodeRef); } } return null; } List convertNodes(Collection nodesList) { return nodesList.stream() .map(this::getJavaNodeByRef) .filter(Objects::nonNull) .collect(Collectors.toList()); } @Nullable public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int pos) { ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos); return getJavaNodeByCodeAnnotation(codeInfo, ann); } @Nullable public JavaNode getClosestJavaNode(ICodeInfo codeInfo, int pos) { ICodeAnnotation ann = codeInfo.getCodeMetadata().getClosestUp(pos); return getJavaNodeByCodeAnnotation(codeInfo, ann); } @Nullable public JavaNode getEnclosingNode(ICodeInfo codeInfo, int pos) { ICodeNodeRef obj = codeInfo.getCodeMetadata().getNodeAt(pos); if (obj == null) { return null; } return getJavaNodeByRef(obj); } public void reloadCodeData() { root.notifyCodeDataListeners(); } public JadxArgs getArgs() { return args; } public JadxPluginManager getPluginManager() { return pluginManager; } public IDecompileScheduler getDecompileScheduler() { return decompileScheduler; } public IJadxEvents events() { return events; } public void addCustomCodeLoader(ICodeLoader customCodeLoader) { customCodeLoaders.add(customCodeLoader); } public List getCustomCodeLoaders() { return customCodeLoaders; } public void addCustomPass(JadxPass pass) { customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass); } @Override public String toString() { return ""jadx decompiler "" + getVersion(); } } ","inputFiles " "/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.extractor.mp4; import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.nullSafeArrayCopy; import static java.lang.Math.max; import static java.lang.annotation.ElementType.TYPE_USE; import android.util.Pair; import android.util.SparseArray; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.audio.Ac4Util; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.extractor.CeaUtil; import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.GaplessInfoHolder; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom; import com.google.android.exoplayer2.metadata.emsg.EventMessage; import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; /** * Extracts data from the FMP4 container format. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @SuppressWarnings(""ConstantField"") @Deprecated public class FragmentedMp4Extractor implements Extractor { /** Factory for {@link FragmentedMp4Extractor} instances. */ public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new FragmentedMp4Extractor()}; /** * Flags controlling the behavior of the extractor. Possible flag values are {@link * #FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME}, {@link #FLAG_WORKAROUND_IGNORE_TFDT_BOX}, * {@link #FLAG_ENABLE_EMSG_TRACK} and {@link #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. */ @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef( flag = true, value = { FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_WORKAROUND_IGNORE_EDIT_LISTS }) public @interface Flags {} /** * Flag to work around an issue in some video streams where every frame is marked as a sync frame. * The workaround overrides the sync frame flags in the stream, forcing them to false except for * the first sample in each segment. * *

This flag does nothing if the stream is not a video stream. */ public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; /** Flag to ignore any tfdt boxes in the stream. */ public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 1 << 1; // 2 /** * Flag to indicate that the extractor should output an event message metadata track. Any event * messages in the stream will be delivered as samples to this track. */ public static final int FLAG_ENABLE_EMSG_TRACK = 1 << 2; // 4 /** Flag to ignore any edit lists in the stream. */ public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1 << 4; // 16 private static final String TAG = ""FragmentedMp4Extractor""; @SuppressWarnings(""ConstantCaseForConstants"") private static final int SAMPLE_GROUP_TYPE_seig = 0x73656967; private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12}; // Extra tracks constants. private static final Format EMSG_FORMAT = new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_EMSG).build(); private static final int EXTRA_TRACKS_BASE_ID = 100; // Parser states. private static final int STATE_READING_ATOM_HEADER = 0; private static final int STATE_READING_ATOM_PAYLOAD = 1; private static final int STATE_READING_ENCRYPTION_DATA = 2; private static final int STATE_READING_SAMPLE_START = 3; private static final int STATE_READING_SAMPLE_CONTINUE = 4; // Workarounds. private final @Flags int flags; @Nullable private final Track sideloadedTrack; // Sideloaded data. private final List closedCaptionFormats; // Track-linked data bundle, accessible as a whole through trackID. private final SparseArray trackBundles; // Temporary arrays. private final ParsableByteArray nalStartCode; private final ParsableByteArray nalPrefix; private final ParsableByteArray nalBuffer; private final byte[] scratchBytes; private final ParsableByteArray scratch; // Adjusts sample timestamps. @Nullable private final TimestampAdjuster timestampAdjuster; private final EventMessageEncoder eventMessageEncoder; // Parser state. private final ParsableByteArray atomHeader; private final ArrayDeque containerAtoms; private final ArrayDeque pendingMetadataSampleInfos; @Nullable private final TrackOutput additionalEmsgTrackOutput; private int parserState; private int atomType; private long atomSize; private int atomHeaderBytesRead; @Nullable private ParsableByteArray atomData; private long endOfMdatPosition; private int pendingMetadataSampleBytes; private long pendingSeekTimeUs; private long durationUs; private long segmentIndexEarliestPresentationTimeUs; @Nullable private TrackBundle currentTrackBundle; private int sampleSize; private int sampleBytesWritten; private int sampleCurrentNalBytesRemaining; private boolean processSeiNalUnitPayload; // Outputs. private ExtractorOutput extractorOutput; private TrackOutput[] emsgTrackOutputs; private TrackOutput[] ceaTrackOutputs; // Whether extractorOutput.seekMap has been called. private boolean haveOutputSeekMap; public FragmentedMp4Extractor() { this(0); } /** * @param flags Flags that control the extractor's behavior. */ public FragmentedMp4Extractor(@Flags int flags) { this(flags, /* timestampAdjuster= */ null); } /** * @param flags Flags that control the extractor's behavior. * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. */ public FragmentedMp4Extractor(@Flags int flags, @Nullable TimestampAdjuster timestampAdjuster) { this(flags, timestampAdjuster, /* sideloadedTrack= */ null, Collections.emptyList()); } /** * @param flags Flags that control the extractor's behavior. * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. * @param sideloadedTrack Sideloaded track information, in the case that the extractor will not * receive a moov box in the input data. Null if a moov box is expected. */ public FragmentedMp4Extractor( @Flags int flags, @Nullable TimestampAdjuster timestampAdjuster, @Nullable Track sideloadedTrack) { this(flags, timestampAdjuster, sideloadedTrack, Collections.emptyList()); } /** * @param flags Flags that control the extractor's behavior. * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. * @param sideloadedTrack Sideloaded track information, in the case that the extractor will not * receive a moov box in the input data. Null if a moov box is expected. * @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed * caption channels to expose. */ public FragmentedMp4Extractor( @Flags int flags, @Nullable TimestampAdjuster timestampAdjuster, @Nullable Track sideloadedTrack, List closedCaptionFormats) { this( flags, timestampAdjuster, sideloadedTrack, closedCaptionFormats, /* additionalEmsgTrackOutput= */ null); } /** * @param flags Flags that control the extractor's behavior. * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. * @param sideloadedTrack Sideloaded track information, in the case that the extractor will not * receive a moov box in the input data. Null if a moov box is expected. * @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed * caption channels to expose. * @param additionalEmsgTrackOutput An extra track output that will receive all emsg messages * targeting the player, even if {@link #FLAG_ENABLE_EMSG_TRACK} is not set. Null if special * handling of emsg messages for players is not required. */ public FragmentedMp4Extractor( @Flags int flags, @Nullable TimestampAdjuster timestampAdjuster, @Nullable Track sideloadedTrack, List closedCaptionFormats, @Nullable TrackOutput additionalEmsgTrackOutput) { this.flags = flags; this.timestampAdjuster = timestampAdjuster; this.sideloadedTrack = sideloadedTrack; this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats); this.additionalEmsgTrackOutput = additionalEmsgTrackOutput; eventMessageEncoder = new EventMessageEncoder(); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalPrefix = new ParsableByteArray(5); nalBuffer = new ParsableByteArray(); scratchBytes = new byte[16]; scratch = new ParsableByteArray(scratchBytes); containerAtoms = new ArrayDeque<>(); pendingMetadataSampleInfos = new ArrayDeque<>(); trackBundles = new SparseArray<>(); durationUs = C.TIME_UNSET; pendingSeekTimeUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; extractorOutput = ExtractorOutput.PLACEHOLDER; emsgTrackOutputs = new TrackOutput[0]; ceaTrackOutputs = new TrackOutput[0]; } @Override public boolean sniff(ExtractorInput input) throws IOException { return Sniffer.sniffFragmented(input); } @Override public void init(ExtractorOutput output) { extractorOutput = output; enterReadingAtomHeaderState(); initExtraTracks(); if (sideloadedTrack != null) { TrackBundle bundle = new TrackBundle( output.track(0, sideloadedTrack.type), new TrackSampleTable( sideloadedTrack, /* offsets= */ new long[0], /* sizes= */ new int[0], /* maximumSize= */ 0, /* timestampsUs= */ new long[0], /* flags= */ new int[0], /* durationUs= */ 0), new DefaultSampleValues( /* sampleDescriptionIndex= */ 0, /* duration= */ 0, /* size= */ 0, /* flags= */ 0)); trackBundles.put(0, bundle); extractorOutput.endTracks(); } } @Override public void seek(long position, long timeUs) { int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { trackBundles.valueAt(i).resetFragmentInfo(); } pendingMetadataSampleInfos.clear(); pendingMetadataSampleBytes = 0; pendingSeekTimeUs = timeUs; containerAtoms.clear(); enterReadingAtomHeaderState(); } @Override public void release() { // Do nothing } @Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { while (true) { switch (parserState) { case STATE_READING_ATOM_HEADER: if (!readAtomHeader(input)) { return Extractor.RESULT_END_OF_INPUT; } break; case STATE_READING_ATOM_PAYLOAD: readAtomPayload(input); break; case STATE_READING_ENCRYPTION_DATA: readEncryptionData(input); break; default: if (readSample(input)) { return RESULT_CONTINUE; } } } } private void enterReadingAtomHeaderState() { parserState = STATE_READING_ATOM_HEADER; atomHeaderBytesRead = 0; } private boolean readAtomHeader(ExtractorInput input) throws IOException { if (atomHeaderBytesRead == 0) { // Read the standard length atom header. if (!input.readFully(atomHeader.getData(), 0, Atom.HEADER_SIZE, true)) { return false; } atomHeaderBytesRead = Atom.HEADER_SIZE; atomHeader.setPosition(0); atomSize = atomHeader.readUnsignedInt(); atomType = atomHeader.readInt(); } if (atomSize == Atom.DEFINES_LARGE_SIZE) { // Read the large size. int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; input.readFully(atomHeader.getData(), Atom.HEADER_SIZE, headerBytesRemaining); atomHeaderBytesRead += headerBytesRemaining; atomSize = atomHeader.readUnsignedLongToLong(); } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { // The atom extends to the end of the file. Note that if the atom is within a container we can // work out its size even if the input length is unknown. long endPosition = input.getLength(); if (endPosition == C.LENGTH_UNSET && !containerAtoms.isEmpty()) { endPosition = containerAtoms.peek().endPosition; } if (endPosition != C.LENGTH_UNSET) { atomSize = endPosition - input.getPosition() + atomHeaderBytesRead; } } if (atomSize < atomHeaderBytesRead) { throw ParserException.createForUnsupportedContainerFeature( ""Atom size less than header length (unsupported).""); } long atomPosition = input.getPosition() - atomHeaderBytesRead; if (atomType == Atom.TYPE_moof || atomType == Atom.TYPE_mdat) { if (!haveOutputSeekMap) { // This must be the first moof or mdat in the stream. extractorOutput.seekMap(new SeekMap.Unseekable(durationUs, atomPosition)); haveOutputSeekMap = true; } } if (atomType == Atom.TYPE_moof) { // The data positions may be updated when parsing the tfhd/trun. int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { TrackFragment fragment = trackBundles.valueAt(i).fragment; fragment.atomPosition = atomPosition; fragment.auxiliaryDataPosition = atomPosition; fragment.dataPosition = atomPosition; } } if (atomType == Atom.TYPE_mdat) { currentTrackBundle = null; endOfMdatPosition = atomPosition + atomSize; parserState = STATE_READING_ENCRYPTION_DATA; return true; } if (shouldParseContainerAtom(atomType)) { long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; containerAtoms.push(new ContainerAtom(atomType, endPosition)); if (atomSize == atomHeaderBytesRead) { processAtomEnded(endPosition); } else { // Start reading the first child atom. enterReadingAtomHeaderState(); } } else if (shouldParseLeafAtom(atomType)) { if (atomHeaderBytesRead != Atom.HEADER_SIZE) { throw ParserException.createForUnsupportedContainerFeature( ""Leaf atom defines extended atom size (unsupported).""); } if (atomSize > Integer.MAX_VALUE) { throw ParserException.createForUnsupportedContainerFeature( ""Leaf atom with length > 2147483647 (unsupported).""); } ParsableByteArray atomData = new ParsableByteArray((int) atomSize); System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE); this.atomData = atomData; parserState = STATE_READING_ATOM_PAYLOAD; } else { if (atomSize > Integer.MAX_VALUE) { throw ParserException.createForUnsupportedContainerFeature( ""Skipping atom with length > 2147483647 (unsupported).""); } atomData = null; parserState = STATE_READING_ATOM_PAYLOAD; } return true; } private void readAtomPayload(ExtractorInput input) throws IOException { int atomPayloadSize = (int) atomSize - atomHeaderBytesRead; @Nullable ParsableByteArray atomData = this.atomData; if (atomData != null) { input.readFully(atomData.getData(), Atom.HEADER_SIZE, atomPayloadSize); onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition()); } else { input.skipFully(atomPayloadSize); } processAtomEnded(input.getPosition()); } private void processAtomEnded(long atomEndPosition) throws ParserException { while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) { onContainerAtomRead(containerAtoms.pop()); } enterReadingAtomHeaderState(); } private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException { if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(leaf); } else if (leaf.type == Atom.TYPE_sidx) { Pair result = parseSidx(leaf.data, inputPosition); segmentIndexEarliestPresentationTimeUs = result.first; extractorOutput.seekMap(result.second); haveOutputSeekMap = true; } else if (leaf.type == Atom.TYPE_emsg) { onEmsgLeafAtomRead(leaf.data); } } private void onContainerAtomRead(ContainerAtom container) throws ParserException { if (container.type == Atom.TYPE_moov) { onMoovContainerAtomRead(container); } else if (container.type == Atom.TYPE_moof) { onMoofContainerAtomRead(container); } else if (!containerAtoms.isEmpty()) { containerAtoms.peek().add(container); } } private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException { checkState(sideloadedTrack == null, ""Unexpected moov box.""); @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren); // Read declaration of track fragments in the moov box. ContainerAtom mvex = checkNotNull(moov.getContainerAtomOfType(Atom.TYPE_mvex)); SparseArray defaultSampleValuesArray = new SparseArray<>(); long duration = C.TIME_UNSET; int mvexChildrenSize = mvex.leafChildren.size(); for (int i = 0; i < mvexChildrenSize; i++) { Atom.LeafAtom atom = mvex.leafChildren.get(i); if (atom.type == Atom.TYPE_trex) { Pair trexData = parseTrex(atom.data); defaultSampleValuesArray.put(trexData.first, trexData.second); } else if (atom.type == Atom.TYPE_mehd) { duration = parseMehd(atom.data); } } // Construction of tracks and sample tables. List sampleTables = parseTraks( moov, new GaplessInfoHolder(), duration, drmInitData, /* ignoreEditLists= */ (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, /* isQuickTime= */ false, this::modifyTrack); int trackCount = sampleTables.size(); if (trackBundles.size() == 0) { // We need to create the track bundles. for (int i = 0; i < trackCount; i++) { TrackSampleTable sampleTable = sampleTables.get(i); Track track = sampleTable.track; TrackBundle trackBundle = new TrackBundle( extractorOutput.track(i, track.type), sampleTable, getDefaultSampleValues(defaultSampleValuesArray, track.id)); trackBundles.put(track.id, trackBundle); durationUs = max(durationUs, track.durationUs); } extractorOutput.endTracks(); } else { checkState(trackBundles.size() == trackCount); for (int i = 0; i < trackCount; i++) { TrackSampleTable sampleTable = sampleTables.get(i); Track track = sampleTable.track; trackBundles .get(track.id) .reset(sampleTable, getDefaultSampleValues(defaultSampleValuesArray, track.id)); } } } @Nullable protected Track modifyTrack(@Nullable Track track) { return track; } private DefaultSampleValues getDefaultSampleValues( SparseArray defaultSampleValuesArray, int trackId) { if (defaultSampleValuesArray.size() == 1) { // Ignore track id if there is only one track to cope with non-matching track indices. // See https://github.com/google/ExoPlayer/issues/4477. return defaultSampleValuesArray.valueAt(/* index= */ 0); } return checkNotNull(defaultSampleValuesArray.get(trackId)); } private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { parseMoof(moof, trackBundles, sideloadedTrack != null, flags, scratchBytes); @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren); if (drmInitData != null) { int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { trackBundles.valueAt(i).updateDrmInitData(drmInitData); } } // If we have a pending seek, advance tracks to their preceding sync frames. if (pendingSeekTimeUs != C.TIME_UNSET) { int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { trackBundles.valueAt(i).seek(pendingSeekTimeUs); } pendingSeekTimeUs = C.TIME_UNSET; } } private void initExtraTracks() { int nextExtraTrackId = EXTRA_TRACKS_BASE_ID; emsgTrackOutputs = new TrackOutput[2]; int emsgTrackOutputCount = 0; if (additionalEmsgTrackOutput != null) { emsgTrackOutputs[emsgTrackOutputCount++] = additionalEmsgTrackOutput; } if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0) { emsgTrackOutputs[emsgTrackOutputCount++] = extractorOutput.track(nextExtraTrackId++, C.TRACK_TYPE_METADATA); } emsgTrackOutputs = nullSafeArrayCopy(emsgTrackOutputs, emsgTrackOutputCount); for (TrackOutput eventMessageTrackOutput : emsgTrackOutputs) { eventMessageTrackOutput.format(EMSG_FORMAT); } ceaTrackOutputs = new TrackOutput[closedCaptionFormats.size()]; for (int i = 0; i < ceaTrackOutputs.length; i++) { TrackOutput output = extractorOutput.track(nextExtraTrackId++, C.TRACK_TYPE_TEXT); output.format(closedCaptionFormats.get(i)); ceaTrackOutputs[i] = output; } } /** Handles an emsg atom (defined in 23009-1). */ private void onEmsgLeafAtomRead(ParsableByteArray atom) { if (emsgTrackOutputs.length == 0) { return; } atom.setPosition(Atom.HEADER_SIZE); int fullAtom = atom.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); String schemeIdUri; String value; long timescale; long presentationTimeDeltaUs = C.TIME_UNSET; // Only set if version == 0 long sampleTimeUs = C.TIME_UNSET; long durationMs; long id; switch (version) { case 0: schemeIdUri = checkNotNull(atom.readNullTerminatedString()); value = checkNotNull(atom.readNullTerminatedString()); timescale = atom.readUnsignedInt(); presentationTimeDeltaUs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale); if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) { sampleTimeUs = segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs; } durationMs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale); id = atom.readUnsignedInt(); break; case 1: timescale = atom.readUnsignedInt(); sampleTimeUs = Util.scaleLargeTimestamp(atom.readUnsignedLongToLong(), C.MICROS_PER_SECOND, timescale); durationMs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale); id = atom.readUnsignedInt(); schemeIdUri = checkNotNull(atom.readNullTerminatedString()); value = checkNotNull(atom.readNullTerminatedString()); break; default: Log.w(TAG, ""Skipping unsupported emsg version: "" + version); return; } byte[] messageData = new byte[atom.bytesLeft()]; atom.readBytes(messageData, /* offset= */ 0, atom.bytesLeft()); EventMessage eventMessage = new EventMessage(schemeIdUri, value, durationMs, id, messageData); ParsableByteArray encodedEventMessage = new ParsableByteArray(eventMessageEncoder.encode(eventMessage)); int sampleSize = encodedEventMessage.bytesLeft(); // Output the sample data. for (TrackOutput emsgTrackOutput : emsgTrackOutputs) { encodedEventMessage.setPosition(0); emsgTrackOutput.sampleData(encodedEventMessage, sampleSize); } // Output the sample metadata. if (sampleTimeUs == C.TIME_UNSET) { // We're processing a v0 emsg atom, which contains a presentation time delta, and cannot yet // calculate its absolute sample timestamp. Defer outputting the metadata until we can. pendingMetadataSampleInfos.addLast( new MetadataSampleInfo( presentationTimeDeltaUs, /* sampleTimeIsRelative= */ true, sampleSize)); pendingMetadataSampleBytes += sampleSize; } else if (!pendingMetadataSampleInfos.isEmpty()) { // We also need to defer outputting metadata if pendingMetadataSampleInfos is non-empty, else // we will output metadata for samples in the wrong order. See: // https://github.com/google/ExoPlayer/issues/9996. pendingMetadataSampleInfos.addLast( new MetadataSampleInfo(sampleTimeUs, /* sampleTimeIsRelative= */ false, sampleSize)); pendingMetadataSampleBytes += sampleSize; } else if (timestampAdjuster != null && !timestampAdjuster.isInitialized()) { // We also need to defer outputting metadata if the timestampAdjuster is not initialized, // else we will set a wrong timestampOffsetUs in timestampAdjuster. See: // https://github.com/androidx/media/issues/356. pendingMetadataSampleInfos.addLast( new MetadataSampleInfo(sampleTimeUs, /* sampleTimeIsRelative= */ false, sampleSize)); pendingMetadataSampleBytes += sampleSize; } else { // We can output the sample metadata immediately. if (timestampAdjuster != null) { sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); } for (TrackOutput emsgTrackOutput : emsgTrackOutputs) { emsgTrackOutput.sampleMetadata( sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, /* offset= */ 0, null); } } } /** Parses a trex atom (defined in 14496-12). */ private static Pair parseTrex(ParsableByteArray trex) { trex.setPosition(Atom.FULL_HEADER_SIZE); int trackId = trex.readInt(); int defaultSampleDescriptionIndex = trex.readInt() - 1; int defaultSampleDuration = trex.readInt(); int defaultSampleSize = trex.readInt(); int defaultSampleFlags = trex.readInt(); return Pair.create( trackId, new DefaultSampleValues( defaultSampleDescriptionIndex, defaultSampleDuration, defaultSampleSize, defaultSampleFlags)); } /** Parses an mehd atom (defined in 14496-12). */ private static long parseMehd(ParsableByteArray mehd) { mehd.setPosition(Atom.HEADER_SIZE); int fullAtom = mehd.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong(); } private static void parseMoof( ContainerAtom moof, SparseArray trackBundles, boolean haveSideloadedTrack, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { int moofContainerChildrenSize = moof.containerChildren.size(); for (int i = 0; i < moofContainerChildrenSize; i++) { Atom.ContainerAtom child = moof.containerChildren.get(i); // TODO: Support multiple traf boxes per track in a single moof. if (child.type == Atom.TYPE_traf) { parseTraf(child, trackBundles, haveSideloadedTrack, flags, extendedTypeScratch); } } } /** Parses a traf atom (defined in 14496-12). */ private static void parseTraf( ContainerAtom traf, SparseArray trackBundles, boolean haveSideloadedTrack, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { LeafAtom tfhd = checkNotNull(traf.getLeafAtomOfType(Atom.TYPE_tfhd)); @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundles, haveSideloadedTrack); if (trackBundle == null) { return; } TrackFragment fragment = trackBundle.fragment; long fragmentDecodeTime = fragment.nextFragmentDecodeTime; boolean fragmentDecodeTimeIncludesMoov = fragment.nextFragmentDecodeTimeIncludesMoov; trackBundle.resetFragmentInfo(); trackBundle.currentlyInFragment = true; @Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { fragment.nextFragmentDecodeTime = parseTfdt(tfdtAtom.data); fragment.nextFragmentDecodeTimeIncludesMoov = true; } else { fragment.nextFragmentDecodeTime = fragmentDecodeTime; fragment.nextFragmentDecodeTimeIncludesMoov = fragmentDecodeTimeIncludesMoov; } parseTruns(traf, trackBundle, flags); @Nullable TrackEncryptionBox encryptionBox = trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox( checkNotNull(fragment.header).sampleDescriptionIndex); @Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { parseSaiz(checkNotNull(encryptionBox), saiz.data, fragment); } @Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); if (saio != null) { parseSaio(saio.data, fragment); } @Nullable LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); if (senc != null) { parseSenc(senc.data, fragment); } parseSampleGroups(traf, encryptionBox != null ? encryptionBox.schemeType : null, fragment); int leafChildrenSize = traf.leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom atom = traf.leafChildren.get(i); if (atom.type == Atom.TYPE_uuid) { parseUuid(atom.data, fragment, extendedTypeScratch); } } } private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, @Flags int flags) throws ParserException { int trunCount = 0; int totalSampleCount = 0; List leafChildren = traf.leafChildren; int leafChildrenSize = leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom atom = leafChildren.get(i); if (atom.type == Atom.TYPE_trun) { ParsableByteArray trunData = atom.data; trunData.setPosition(Atom.FULL_HEADER_SIZE); int trunSampleCount = trunData.readUnsignedIntToInt(); if (trunSampleCount > 0) { totalSampleCount += trunSampleCount; trunCount++; } } } trackBundle.currentTrackRunIndex = 0; trackBundle.currentSampleInTrackRun = 0; trackBundle.currentSampleIndex = 0; trackBundle.fragment.initTables(trunCount, totalSampleCount); int trunIndex = 0; int trunStartPosition = 0; for (int i = 0; i < leafChildrenSize; i++) { LeafAtom trun = leafChildren.get(i); if (trun.type == Atom.TYPE_trun) { trunStartPosition = parseTrun(trackBundle, trunIndex++, flags, trun.data, trunStartPosition); } } } private static void parseSaiz( TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out) throws ParserException { int vectorSize = encryptionBox.perSampleIvSize; saiz.setPosition(Atom.HEADER_SIZE); int fullAtom = saiz.readInt(); int flags = Atom.parseFullAtomFlags(fullAtom); if ((flags & 0x01) == 1) { saiz.skipBytes(8); } int defaultSampleInfoSize = saiz.readUnsignedByte(); int sampleCount = saiz.readUnsignedIntToInt(); if (sampleCount > out.sampleCount) { throw ParserException.createForMalformedContainer( ""Saiz sample count "" + sampleCount + "" is greater than fragment sample count"" + out.sampleCount, /* cause= */ null); } int totalSize = 0; if (defaultSampleInfoSize == 0) { boolean[] sampleHasSubsampleEncryptionTable = out.sampleHasSubsampleEncryptionTable; for (int i = 0; i < sampleCount; i++) { int sampleInfoSize = saiz.readUnsignedByte(); totalSize += sampleInfoSize; sampleHasSubsampleEncryptionTable[i] = sampleInfoSize > vectorSize; } } else { boolean subsampleEncryption = defaultSampleInfoSize > vectorSize; totalSize += defaultSampleInfoSize * sampleCount; Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); } Arrays.fill(out.sampleHasSubsampleEncryptionTable, sampleCount, out.sampleCount, false); if (totalSize > 0) { out.initEncryptionData(totalSize); } } /** * Parses a saio atom (defined in 14496-12). * * @param saio The saio atom to decode. * @param out The {@link TrackFragment} to populate with data from the saio atom. */ private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException { saio.setPosition(Atom.HEADER_SIZE); int fullAtom = saio.readInt(); int flags = Atom.parseFullAtomFlags(fullAtom); if ((flags & 0x01) == 1) { saio.skipBytes(8); } int entryCount = saio.readUnsignedIntToInt(); if (entryCount != 1) { // We only support one trun element currently, so always expect one entry. throw ParserException.createForMalformedContainer( ""Unexpected saio entry count: "" + entryCount, /* cause= */ null); } int version = Atom.parseFullAtomVersion(fullAtom); out.auxiliaryDataPosition += version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong(); } /** * Parses a tfhd atom (defined in 14496-12), updates the corresponding {@link TrackFragment} and * returns the {@link TrackBundle} of the corresponding {@link Track}. If the tfhd does not refer * to any {@link TrackBundle}, {@code null} is returned and no changes are made. * * @param tfhd The tfhd atom to decode. * @param trackBundles The track bundles, one of which corresponds to the tfhd atom being parsed. * @param haveSideloadedTrack Whether {@code trackBundles} contains a single bundle corresponding * to a side-loaded track. * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd * does not refer to any {@link TrackBundle}. */ @Nullable private static TrackBundle parseTfhd( ParsableByteArray tfhd, SparseArray trackBundles, boolean haveSideloadedTrack) { tfhd.setPosition(Atom.HEADER_SIZE); int fullAtom = tfhd.readInt(); int atomFlags = Atom.parseFullAtomFlags(fullAtom); int trackId = tfhd.readInt(); @Nullable TrackBundle trackBundle = haveSideloadedTrack ? trackBundles.valueAt(0) : trackBundles.get(trackId); if (trackBundle == null) { return null; } if ((atomFlags & 0x01 /* base_data_offset_present */) != 0) { long baseDataPosition = tfhd.readUnsignedLongToLong(); trackBundle.fragment.dataPosition = baseDataPosition; trackBundle.fragment.auxiliaryDataPosition = baseDataPosition; } DefaultSampleValues defaultSampleValues = trackBundle.defaultSampleValues; int defaultSampleDescriptionIndex = ((atomFlags & 0x02 /* default_sample_description_index_present */) != 0) ? tfhd.readInt() - 1 : defaultSampleValues.sampleDescriptionIndex; int defaultSampleDuration = ((atomFlags & 0x08 /* default_sample_duration_present */) != 0) ? tfhd.readInt() : defaultSampleValues.duration; int defaultSampleSize = ((atomFlags & 0x10 /* default_sample_size_present */) != 0) ? tfhd.readInt() : defaultSampleValues.size; int defaultSampleFlags = ((atomFlags & 0x20 /* default_sample_flags_present */) != 0) ? tfhd.readInt() : defaultSampleValues.flags; trackBundle.fragment.header = new DefaultSampleValues( defaultSampleDescriptionIndex, defaultSampleDuration, defaultSampleSize, defaultSampleFlags); return trackBundle; } /** * Parses a tfdt atom (defined in 14496-12). * * @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the * media, expressed in the media's timescale. */ private static long parseTfdt(ParsableByteArray tfdt) { tfdt.setPosition(Atom.HEADER_SIZE); int fullAtom = tfdt.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt(); } private static boolean isEdtsListDurationForEntireMediaTimeline(Track track) { // Currently we only support a single edit that moves the entire media timeline (indicated by // duration == 0 or (editListDurationUs + editListMediaTimeUs) >= track duration. // Other uses of edit lists are uncommon and unsupported. if (track.editListDurations == null || track.editListDurations.length != 1 || track.editListMediaTimes == null) { return false; } if (track.editListDurations[0] == 0) { return true; } long editListEndMediaTimeUs = Util.scaleLargeTimestamp( track.editListDurations[0] + track.editListMediaTimes[0], C.MICROS_PER_SECOND, track.movieTimescale); return editListEndMediaTimeUs >= track.durationUs; } /** * Parses a trun atom (defined in 14496-12). * * @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into which * parsed data should be placed. * @param index Index of the track run in the fragment. * @param flags Flags to allow any required workaround to be executed. * @param trun The trun atom to decode. * @return The starting position of samples for the next run. */ private static int parseTrun( TrackBundle trackBundle, int index, @Flags int flags, ParsableByteArray trun, int trackRunStart) throws ParserException { trun.setPosition(Atom.HEADER_SIZE); int fullAtom = trun.readInt(); int atomFlags = Atom.parseFullAtomFlags(fullAtom); Track track = trackBundle.moovSampleTable.track; TrackFragment fragment = trackBundle.fragment; DefaultSampleValues defaultSampleValues = castNonNull(fragment.header); fragment.trunLength[index] = trun.readUnsignedIntToInt(); fragment.trunDataPosition[index] = fragment.dataPosition; if ((atomFlags & 0x01 /* data_offset_present */) != 0) { fragment.trunDataPosition[index] += trun.readInt(); } boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0; int firstSampleFlags = defaultSampleValues.flags; if (firstSampleFlagsPresent) { firstSampleFlags = trun.readInt(); } boolean sampleDurationsPresent = (atomFlags & 0x100 /* sample_duration_present */) != 0; boolean sampleSizesPresent = (atomFlags & 0x200 /* sample_size_present */) != 0; boolean sampleFlagsPresent = (atomFlags & 0x400 /* sample_flags_present */) != 0; boolean sampleCompositionTimeOffsetsPresent = (atomFlags & 0x800 /* sample_composition_time_offsets_present */) != 0; // Offset to the entire video timeline. In the presence of B-frames this is usually used to // ensure that the first frame's presentation timestamp is zero. long edtsOffset = 0; // Currently we only support a single edit that moves the entire media timeline. if (isEdtsListDurationForEntireMediaTimeline(track)) { edtsOffset = castNonNull(track.editListMediaTimes)[0]; } int[] sampleSizeTable = fragment.sampleSizeTable; long[] samplePresentationTimesUs = fragment.samplePresentationTimesUs; boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable; boolean workaroundEveryVideoFrameIsSyncFrame = track.type == C.TRACK_TYPE_VIDEO && (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0; int trackRunEnd = trackRunStart + fragment.trunLength[index]; long timescale = track.timescale; long cumulativeTime = fragment.nextFragmentDecodeTime; for (int i = trackRunStart; i < trackRunEnd; i++) { // Use trun values if present, otherwise tfhd, otherwise trex. int sampleDuration = checkNonNegative(sampleDurationsPresent ? trun.readInt() : defaultSampleValues.duration); int sampleSize = checkNonNegative(sampleSizesPresent ? trun.readInt() : defaultSampleValues.size); int sampleFlags = sampleFlagsPresent ? trun.readInt() : (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags : defaultSampleValues.flags; int sampleCompositionTimeOffset = 0; if (sampleCompositionTimeOffsetsPresent) { // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in // version 0 trun boxes, however a significant number of streams violate the spec and use // signed integers instead. It's safe to always decode sample offsets as signed integers // here, because unsigned integers will still be parsed correctly (unless their top bit is // set, which is never true in practice because sample offsets are always small). sampleCompositionTimeOffset = trun.readInt(); } long samplePresentationTime = cumulativeTime + sampleCompositionTimeOffset - edtsOffset; samplePresentationTimesUs[i] = Util.scaleLargeTimestamp(samplePresentationTime, C.MICROS_PER_SECOND, timescale); if (!fragment.nextFragmentDecodeTimeIncludesMoov) { samplePresentationTimesUs[i] += trackBundle.moovSampleTable.durationUs; } sampleSizeTable[i] = sampleSize; sampleIsSyncFrameTable[i] = ((sampleFlags >> 16) & 0x1) == 0 && (!workaroundEveryVideoFrameIsSyncFrame || i == 0); cumulativeTime += sampleDuration; } fragment.nextFragmentDecodeTime = cumulativeTime; return trackRunEnd; } private static int checkNonNegative(int value) throws ParserException { if (value < 0) { throw ParserException.createForMalformedContainer( ""Unexpected negative value: "" + value, /* cause= */ null); } return value; } private static void parseUuid( ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch) throws ParserException { uuid.setPosition(Atom.HEADER_SIZE); uuid.readBytes(extendedTypeScratch, 0, 16); // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox. if (!Arrays.equals(extendedTypeScratch, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) { return; } // Except for the extended type, this box is identical to a SENC box. See ""Portable encoding of // audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al, // Section 5.3.2.1."" parseSenc(uuid, 16, out); } private static void parseSenc(ParsableByteArray senc, TrackFragment out) throws ParserException { parseSenc(senc, 0, out); } private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) throws ParserException { senc.setPosition(Atom.HEADER_SIZE + offset); int fullAtom = senc.readInt(); int flags = Atom.parseFullAtomFlags(fullAtom); if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) { // TODO: Implement this. throw ParserException.createForUnsupportedContainerFeature( ""Overriding TrackEncryptionBox parameters is unsupported.""); } boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0; int sampleCount = senc.readUnsignedIntToInt(); if (sampleCount == 0) { // Samples are unencrypted. Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, out.sampleCount, false); return; } else if (sampleCount != out.sampleCount) { throw ParserException.createForMalformedContainer( ""Senc sample count "" + sampleCount + "" is different from fragment sample count"" + out.sampleCount, /* cause= */ null); } Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); out.initEncryptionData(senc.bytesLeft()); out.fillEncryptionData(senc); } private static void parseSampleGroups( ContainerAtom traf, @Nullable String schemeType, TrackFragment out) throws ParserException { // Find sbgp and sgpd boxes with grouping_type == seig. @Nullable ParsableByteArray sbgp = null; @Nullable ParsableByteArray sgpd = null; for (int i = 0; i < traf.leafChildren.size(); i++) { LeafAtom leafAtom = traf.leafChildren.get(i); ParsableByteArray leafAtomData = leafAtom.data; if (leafAtom.type == Atom.TYPE_sbgp) { leafAtomData.setPosition(Atom.FULL_HEADER_SIZE); if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) { sbgp = leafAtomData; } } else if (leafAtom.type == Atom.TYPE_sgpd) { leafAtomData.setPosition(Atom.FULL_HEADER_SIZE); if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) { sgpd = leafAtomData; } } } if (sbgp == null || sgpd == null) { return; } sbgp.setPosition(Atom.HEADER_SIZE); int sbgpVersion = Atom.parseFullAtomVersion(sbgp.readInt()); sbgp.skipBytes(4); // grouping_type == seig. if (sbgpVersion == 1) { sbgp.skipBytes(4); // grouping_type_parameter. } if (sbgp.readInt() != 1) { // entry_count. throw ParserException.createForUnsupportedContainerFeature( ""Entry count in sbgp != 1 (unsupported).""); } sgpd.setPosition(Atom.HEADER_SIZE); int sgpdVersion = Atom.parseFullAtomVersion(sgpd.readInt()); sgpd.skipBytes(4); // grouping_type == seig. if (sgpdVersion == 1) { if (sgpd.readUnsignedInt() == 0) { throw ParserException.createForUnsupportedContainerFeature( ""Variable length description in sgpd found (unsupported)""); } } else if (sgpdVersion >= 2) { sgpd.skipBytes(4); // default_sample_description_index. } if (sgpd.readUnsignedInt() != 1) { // entry_count. throw ParserException.createForUnsupportedContainerFeature( ""Entry count in sgpd != 1 (unsupported).""); } // CencSampleEncryptionInformationGroupEntry sgpd.skipBytes(1); // reserved = 0. int patternByte = sgpd.readUnsignedByte(); int cryptByteBlock = (patternByte & 0xF0) >> 4; int skipByteBlock = patternByte & 0x0F; boolean isProtected = sgpd.readUnsignedByte() == 1; if (!isProtected) { return; } int perSampleIvSize = sgpd.readUnsignedByte(); byte[] keyId = new byte[16]; sgpd.readBytes(keyId, 0, keyId.length); @Nullable byte[] constantIv = null; if (perSampleIvSize == 0) { int constantIvSize = sgpd.readUnsignedByte(); constantIv = new byte[constantIvSize]; sgpd.readBytes(constantIv, 0, constantIvSize); } out.definesEncryptionData = true; out.trackEncryptionBox = new TrackEncryptionBox( isProtected, schemeType, perSampleIvSize, keyId, cryptByteBlock, skipByteBlock, constantIv); } /** * Parses a sidx atom (defined in 14496-12). * * @param atom The atom data. * @param inputPosition The input position of the first byte after the atom. * @return A pair consisting of the earliest presentation time in microseconds, and the parsed * {@link ChunkIndex}. */ private static Pair parseSidx(ParsableByteArray atom, long inputPosition) throws ParserException { atom.setPosition(Atom.HEADER_SIZE); int fullAtom = atom.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); atom.skipBytes(4); long timescale = atom.readUnsignedInt(); long earliestPresentationTime; long offset = inputPosition; if (version == 0) { earliestPresentationTime = atom.readUnsignedInt(); offset += atom.readUnsignedInt(); } else { earliestPresentationTime = atom.readUnsignedLongToLong(); offset += atom.readUnsignedLongToLong(); } long earliestPresentationTimeUs = Util.scaleLargeTimestamp(earliestPresentationTime, C.MICROS_PER_SECOND, timescale); atom.skipBytes(2); int referenceCount = atom.readUnsignedShort(); int[] sizes = new int[referenceCount]; long[] offsets = new long[referenceCount]; long[] durationsUs = new long[referenceCount]; long[] timesUs = new long[referenceCount]; long time = earliestPresentationTime; long timeUs = earliestPresentationTimeUs; for (int i = 0; i < referenceCount; i++) { int firstInt = atom.readInt(); int type = 0x80000000 & firstInt; if (type != 0) { throw ParserException.createForMalformedContainer( ""Unhandled indirect reference"", /* cause= */ null); } long referenceDuration = atom.readUnsignedInt(); sizes[i] = 0x7FFFFFFF & firstInt; offsets[i] = offset; // Calculate time and duration values such that any rounding errors are consistent. i.e. That // timesUs[i] + durationsUs[i] == timesUs[i + 1]. timesUs[i] = timeUs; time += referenceDuration; timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale); durationsUs[i] = timeUs - timesUs[i]; atom.skipBytes(4); offset += sizes[i]; } return Pair.create( earliestPresentationTimeUs, new ChunkIndex(sizes, offsets, durationsUs, timesUs)); } private void readEncryptionData(ExtractorInput input) throws IOException { @Nullable TrackBundle nextTrackBundle = null; long nextDataOffset = Long.MAX_VALUE; int trackBundlesSize = trackBundles.size(); for (int i = 0; i < trackBundlesSize; i++) { TrackFragment trackFragment = trackBundles.valueAt(i).fragment; if (trackFragment.sampleEncryptionDataNeedsFill && trackFragment.auxiliaryDataPosition < nextDataOffset) { nextDataOffset = trackFragment.auxiliaryDataPosition; nextTrackBundle = trackBundles.valueAt(i); } } if (nextTrackBundle == null) { parserState = STATE_READING_SAMPLE_START; return; } int bytesToSkip = (int) (nextDataOffset - input.getPosition()); if (bytesToSkip < 0) { throw ParserException.createForMalformedContainer( ""Offset to encryption data was negative."", /* cause= */ null); } input.skipFully(bytesToSkip); nextTrackBundle.fragment.fillEncryptionData(input); } /** * Attempts to read the next sample in the current mdat atom. The read sample may be output or * skipped. * *

If there are no more samples in the current mdat atom then the parser state is transitioned * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned. * *

It is possible for a sample to be partially read in the case that an exception is thrown. In * this case the method can be called again to read the remainder of the sample. * * @param input The {@link ExtractorInput} from which to read data. * @return Whether a sample was read. The read sample may have been output or skipped. False * indicates that there are no samples left to read in the current mdat. * @throws IOException If an error occurs reading from the input. */ private boolean readSample(ExtractorInput input) throws IOException { @Nullable TrackBundle trackBundle = currentTrackBundle; if (trackBundle == null) { trackBundle = getNextTrackBundle(trackBundles); if (trackBundle == null) { // We've run out of samples in the current mdat. Discard any trailing data and prepare to // read the header of the next atom. int bytesToSkip = (int) (endOfMdatPosition - input.getPosition()); if (bytesToSkip < 0) { throw ParserException.createForMalformedContainer( ""Offset to end of mdat was negative."", /* cause= */ null); } input.skipFully(bytesToSkip); enterReadingAtomHeaderState(); return false; } long nextDataPosition = trackBundle.getCurrentSampleOffset(); // We skip bytes preceding the next sample to read. int bytesToSkip = (int) (nextDataPosition - input.getPosition()); if (bytesToSkip < 0) { // Assume the sample data must be contiguous in the mdat with no preceding data. Log.w(TAG, ""Ignoring negative offset to sample data.""); bytesToSkip = 0; } input.skipFully(bytesToSkip); currentTrackBundle = trackBundle; } if (parserState == STATE_READING_SAMPLE_START) { sampleSize = trackBundle.getCurrentSampleSize(); if (trackBundle.currentSampleIndex < trackBundle.firstSampleToOutputIndex) { input.skipFully(sampleSize); trackBundle.skipSampleEncryptionData(); if (!trackBundle.next()) { currentTrackBundle = null; } parserState = STATE_READING_SAMPLE_START; return true; } if (trackBundle.moovSampleTable.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { sampleSize -= Atom.HEADER_SIZE; input.skipFully(Atom.HEADER_SIZE); } if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) { // AC4 samples need to be prefixed with a clear sample header. sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE); Ac4Util.getAc4SampleHeader(sampleSize, scratch); trackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE); sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE; } else { sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0); } sampleSize += sampleBytesWritten; parserState = STATE_READING_SAMPLE_CONTINUE; sampleCurrentNalBytesRemaining = 0; } Track track = trackBundle.moovSampleTable.track; TrackOutput output = trackBundle.output; long sampleTimeUs = trackBundle.getCurrentSamplePresentationTimeUs(); if (timestampAdjuster != null) { sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); } if (track.nalUnitLengthFieldLength != 0) { // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case // they're only 1 or 2 bytes long. byte[] nalPrefixData = nalPrefix.getData(); nalPrefixData[0] = 0; nalPrefixData[1] = 0; nalPrefixData[2] = 0; int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1; int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength; // NAL units are length delimited, but the decoder requires start code delimited units. // Loop until we've written the sample to the track output, replacing length delimiters with // start codes as we encounter them. while (sampleBytesWritten < sampleSize) { if (sampleCurrentNalBytesRemaining == 0) { // Read the NAL length so that we know where we find the next one, and its type. input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength); nalPrefix.setPosition(0); int nalLengthInt = nalPrefix.readInt(); if (nalLengthInt < 1) { throw ParserException.createForMalformedContainer( ""Invalid NAL length"", /* cause= */ null); } sampleCurrentNalBytesRemaining = nalLengthInt - 1; // Write a start code for the current NAL unit. nalStartCode.setPosition(0); output.sampleData(nalStartCode, 4); // Write the NAL unit type byte. output.sampleData(nalPrefix, 1); processSeiNalUnitPayload = ceaTrackOutputs.length > 0 && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]); sampleBytesWritten += 5; sampleSize += nalUnitLengthFieldLengthDiff; } else { int writtenBytes; if (processSeiNalUnitPayload) { // Read and write the payload of the SEI NAL unit. nalBuffer.reset(sampleCurrentNalBytesRemaining); input.readFully(nalBuffer.getData(), 0, sampleCurrentNalBytesRemaining); output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining); writtenBytes = sampleCurrentNalBytesRemaining; // Unescape and process the SEI NAL unit. int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.getData(), nalBuffer.limit()); // If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte. nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0); nalBuffer.setLimit(unescapedLength); CeaUtil.consume(sampleTimeUs, nalBuffer, ceaTrackOutputs); } else { // Write the payload of the NAL unit. writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false); } sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } } } else { while (sampleBytesWritten < sampleSize) { int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; } } @C.BufferFlags int sampleFlags = trackBundle.getCurrentSampleFlags(); // Encryption data. @Nullable TrackOutput.CryptoData cryptoData = null; @Nullable TrackEncryptionBox encryptionBox = trackBundle.getEncryptionBoxIfEncrypted(); if (encryptionBox != null) { cryptoData = encryptionBox.cryptoData; } output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData); // After we have the sampleTimeUs, we can commit all the pending metadata samples outputPendingMetadataSamples(sampleTimeUs); if (!trackBundle.next()) { currentTrackBundle = null; } parserState = STATE_READING_SAMPLE_START; return true; } /** * Called immediately after outputting a non-metadata sample, to output any pending metadata * samples. * * @param sampleTimeUs The timestamp of the non-metadata sample that was just output. */ private void outputPendingMetadataSamples(long sampleTimeUs) { while (!pendingMetadataSampleInfos.isEmpty()) { MetadataSampleInfo metadataSampleInfo = pendingMetadataSampleInfos.removeFirst(); pendingMetadataSampleBytes -= metadataSampleInfo.size; long [MASK] = metadataSampleInfo.sampleTimeUs; if (metadataSampleInfo.sampleTimeIsRelative) { // The metadata sample timestamp is relative to the timestamp of the non-metadata sample // that was just output. Make it absolute. [MASK] += sampleTimeUs; } if (timestampAdjuster != null) { [MASK] = timestampAdjuster.adjustSampleTimestamp( [MASK] ); } for (TrackOutput emsgTrackOutput : emsgTrackOutputs) { emsgTrackOutput.sampleMetadata( [MASK] , C.BUFFER_FLAG_KEY_FRAME, metadataSampleInfo.size, pendingMetadataSampleBytes, null); } } } /** * Returns the {@link TrackBundle} whose sample has the earliest file position out of those yet to * be consumed, or null if all have been consumed. */ @Nullable private static TrackBundle getNextTrackBundle(SparseArray trackBundles) { @Nullable TrackBundle nextTrackBundle = null; long nextSampleOffset = Long.MAX_VALUE; int trackBundlesSize = trackBundles.size(); for (int i = 0; i < trackBundlesSize; i++) { TrackBundle trackBundle = trackBundles.valueAt(i); if ((!trackBundle.currentlyInFragment && trackBundle.currentSampleIndex == trackBundle.moovSampleTable.sampleCount) || (trackBundle.currentlyInFragment && trackBundle.currentTrackRunIndex == trackBundle.fragment.trunCount)) { // This track sample table or fragment contains no more runs in the next mdat box. } else { long sampleOffset = trackBundle.getCurrentSampleOffset(); if (sampleOffset < nextSampleOffset) { nextTrackBundle = trackBundle; nextSampleOffset = sampleOffset; } } } return nextTrackBundle; } /** Returns DrmInitData from leaf atoms. */ @Nullable private static DrmInitData getDrmInitDataFromAtoms(List leafChildren) { @Nullable ArrayList schemeDatas = null; int leafChildrenSize = leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom child = leafChildren.get(i); if (child.type == Atom.TYPE_pssh) { if (schemeDatas == null) { schemeDatas = new ArrayList<>(); } byte[] psshData = child.data.getData(); @Nullable UUID uuid = PsshAtomUtil.parseUuid(psshData); if (uuid == null) { Log.w(TAG, ""Skipped pssh atom (failed to extract uuid)""); } else { schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); } } } return schemeDatas == null ? null : new DrmInitData(schemeDatas); } /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */ private static boolean shouldParseLeafAtom(int atom) { return atom == Atom.TYPE_hdlr || atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_sidx || atom == Atom.TYPE_stsd || atom == Atom.TYPE_stts || atom == Atom.TYPE_ctts || atom == Atom.TYPE_stsc || atom == Atom.TYPE_stsz || atom == Atom.TYPE_stz2 || atom == Atom.TYPE_stco || atom == Atom.TYPE_co64 || atom == Atom.TYPE_stss || atom == Atom.TYPE_tfdt || atom == Atom.TYPE_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid || atom == Atom.TYPE_sbgp || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_elst || atom == Atom.TYPE_mehd || atom == Atom.TYPE_emsg; } /** Returns whether the extractor should decode a container atom with type {@code atom}. */ private static boolean shouldParseContainerAtom(int atom) { return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia || atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_moof || atom == Atom.TYPE_traf || atom == Atom.TYPE_mvex || atom == Atom.TYPE_edts; } /** Holds data corresponding to a metadata sample. */ private static final class MetadataSampleInfo { public final long sampleTimeUs; public final boolean sampleTimeIsRelative; public final int size; public MetadataSampleInfo(long sampleTimeUs, boolean sampleTimeIsRelative, int size) { this.sampleTimeUs = sampleTimeUs; this.sampleTimeIsRelative = sampleTimeIsRelative; this.size = size; } } /** Holds data corresponding to a single track. */ private static final class TrackBundle { private static final int SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH = 8; public final TrackOutput output; public final TrackFragment fragment; public final ParsableByteArray scratch; public TrackSampleTable moovSampleTable; public DefaultSampleValues defaultSampleValues; public int currentSampleIndex; public int currentSampleInTrackRun; public int currentTrackRunIndex; public int firstSampleToOutputIndex; private final ParsableByteArray encryptionSignalByte; private final ParsableByteArray defaultInitializationVector; private boolean currentlyInFragment; public TrackBundle( TrackOutput output, TrackSampleTable moovSampleTable, DefaultSampleValues defaultSampleValues) { this.output = output; this.moovSampleTable = moovSampleTable; this.defaultSampleValues = defaultSampleValues; fragment = new TrackFragment(); scratch = new ParsableByteArray(); encryptionSignalByte = new ParsableByteArray(1); defaultInitializationVector = new ParsableByteArray(); reset(moovSampleTable, defaultSampleValues); } public void reset(TrackSampleTable moovSampleTable, DefaultSampleValues defaultSampleValues) { this.moovSampleTable = moovSampleTable; this.defaultSampleValues = defaultSampleValues; output.format(moovSampleTable.track.format); resetFragmentInfo(); } public void updateDrmInitData(DrmInitData drmInitData) { @Nullable TrackEncryptionBox encryptionBox = moovSampleTable.track.getSampleDescriptionEncryptionBox( castNonNull(fragment.header).sampleDescriptionIndex); @Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null; DrmInitData updatedDrmInitData = drmInitData.copyWithSchemeType(schemeType); Format format = moovSampleTable.track.format.buildUpon().setDrmInitData(updatedDrmInitData).build(); output.format(format); } /** Resets the current fragment, sample indices and {@link #currentlyInFragment} boolean. */ public void resetFragmentInfo() { fragment.reset(); currentSampleIndex = 0; currentTrackRunIndex = 0; currentSampleInTrackRun = 0; firstSampleToOutputIndex = 0; currentlyInFragment = false; } /** * Advances {@link #firstSampleToOutputIndex} to point to the sync sample at or before the * specified seek time in the current fragment. * * @param timeUs The seek time, in microseconds. */ public void seek(long timeUs) { int searchIndex = currentSampleIndex; while (searchIndex < fragment.sampleCount && fragment.getSamplePresentationTimeUs(searchIndex) <= timeUs) { if (fragment.sampleIsSyncFrameTable[searchIndex]) { firstSampleToOutputIndex = searchIndex; } searchIndex++; } } /** Returns the presentation time of the current sample in microseconds. */ public long getCurrentSamplePresentationTimeUs() { return !currentlyInFragment ? moovSampleTable.timestampsUs[currentSampleIndex] : fragment.getSamplePresentationTimeUs(currentSampleIndex); } /** Returns the byte offset of the current sample. */ public long getCurrentSampleOffset() { return !currentlyInFragment ? moovSampleTable.offsets[currentSampleIndex] : fragment.trunDataPosition[currentTrackRunIndex]; } /** Returns the size of the current sample in bytes. */ public int getCurrentSampleSize() { return !currentlyInFragment ? moovSampleTable.sizes[currentSampleIndex] : fragment.sampleSizeTable[currentSampleIndex]; } /** Returns the {@link C.BufferFlags} corresponding to the current sample. */ public @C.BufferFlags int getCurrentSampleFlags() { int flags = !currentlyInFragment ? moovSampleTable.flags[currentSampleIndex] : (fragment.sampleIsSyncFrameTable[currentSampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); if (getEncryptionBoxIfEncrypted() != null) { flags |= C.BUFFER_FLAG_ENCRYPTED; } return flags; } /** * Advances the indices in the bundle to point to the next sample in the sample table (if it has * not reached the fragments yet) or in the current fragment. * *

If the current sample is the last one in the sample table, then the advanced state will be * {@code currentSampleIndex == moovSampleTable.sampleCount}. If the current sample is the last * one in the current fragment, then the advanced state will be {@code currentSampleIndex == * fragment.sampleCount}, {@code currentTrackRunIndex == fragment.trunCount} and {@code * #currentSampleInTrackRun == 0}. * * @return Whether this {@link TrackBundle} can be used to read the next sample without * recomputing the next {@link TrackBundle}. */ public boolean next() { currentSampleIndex++; if (!currentlyInFragment) { return false; } currentSampleInTrackRun++; if (currentSampleInTrackRun == fragment.trunLength[currentTrackRunIndex]) { currentTrackRunIndex++; currentSampleInTrackRun = 0; return false; } return true; } /** * Outputs the encryption data for the current sample. * *

This is not supported yet for samples specified in the sample table. * * @param sampleSize The size of the current sample in bytes, excluding any additional clear * header that will be prefixed to the sample by the extractor. * @param clearHeaderSize The size of a clear header that will be prefixed to the sample by the * extractor, or 0. * @return The number of written bytes. */ public int outputSampleEncryptionData(int sampleSize, int clearHeaderSize) { @Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted(); if (encryptionBox == null) { return 0; } ParsableByteArray initializationVectorData; int vectorSize; if (encryptionBox.perSampleIvSize != 0) { initializationVectorData = fragment.sampleEncryptionData; vectorSize = encryptionBox.perSampleIvSize; } else { // The default initialization vector should be used. byte[] initVectorData = castNonNull(encryptionBox.defaultInitializationVector); defaultInitializationVector.reset(initVectorData, initVectorData.length); initializationVectorData = defaultInitializationVector; vectorSize = initVectorData.length; } boolean haveSubsampleEncryptionTable = fragment.sampleHasSubsampleEncryptionTable(currentSampleIndex); boolean writeSubsampleEncryptionData = haveSubsampleEncryptionTable || clearHeaderSize != 0; // Write the signal byte, containing the vector size and the subsample encryption flag. encryptionSignalByte.getData()[0] = (byte) (vectorSize | (writeSubsampleEncryptionData ? 0x80 : 0)); encryptionSignalByte.setPosition(0); output.sampleData(encryptionSignalByte, 1, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION); // Write the vector. output.sampleData( initializationVectorData, vectorSize, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION); if (!writeSubsampleEncryptionData) { return 1 + vectorSize; } if (!haveSubsampleEncryptionTable) { // The sample is fully encrypted, except for the additional clear header that the extractor // is going to prefix. We need to synthesize subsample encryption data that takes the header // into account. scratch.reset(SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH); // subsampleCount = 1 (unsigned short) byte[] data = scratch.getData(); data[0] = (byte) 0; data[1] = (byte) 1; // clearDataSize = clearHeaderSize (unsigned short) data[2] = (byte) ((clearHeaderSize >> 8) & 0xFF); data[3] = (byte) (clearHeaderSize & 0xFF); // encryptedDataSize = sampleSize (unsigned int) data[4] = (byte) ((sampleSize >> 24) & 0xFF); data[5] = (byte) ((sampleSize >> 16) & 0xFF); data[6] = (byte) ((sampleSize >> 8) & 0xFF); data[7] = (byte) (sampleSize & 0xFF); output.sampleData( scratch, SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION); return 1 + vectorSize + SINGLE_SUBSAMPLE_ENCRYPTION_DATA_LENGTH; } ParsableByteArray subsampleEncryptionData = fragment.sampleEncryptionData; int subsampleCount = subsampleEncryptionData.readUnsignedShort(); subsampleEncryptionData.skipBytes(-2); int subsampleDataLength = 2 + 6 * subsampleCount; if (clearHeaderSize != 0) { // We need to account for the additional clear header by adding clearHeaderSize to // clearDataSize for the first subsample specified in the subsample encryption data. scratch.reset(subsampleDataLength); byte[] scratchData = scratch.getData(); subsampleEncryptionData.readBytes(scratchData, /* offset= */ 0, subsampleDataLength); int clearDataSize = (scratchData[2] & 0xFF) << 8 | (scratchData[3] & 0xFF); int adjustedClearDataSize = clearDataSize + clearHeaderSize; scratchData[2] = (byte) ((adjustedClearDataSize >> 8) & 0xFF); scratchData[3] = (byte) (adjustedClearDataSize & 0xFF); subsampleEncryptionData = scratch; } output.sampleData( subsampleEncryptionData, subsampleDataLength, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION); return 1 + vectorSize + subsampleDataLength; } /** * Skips the encryption data for the current sample. * *

This is not supported yet for samples specified in the sample table. */ public void skipSampleEncryptionData() { @Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted(); if (encryptionBox == null) { return; } ParsableByteArray sampleEncryptionData = fragment.sampleEncryptionData; if (encryptionBox.perSampleIvSize != 0) { sampleEncryptionData.skipBytes(encryptionBox.perSampleIvSize); } if (fragment.sampleHasSubsampleEncryptionTable(currentSampleIndex)) { sampleEncryptionData.skipBytes(6 * sampleEncryptionData.readUnsignedShort()); } } @Nullable public TrackEncryptionBox getEncryptionBoxIfEncrypted() { if (!currentlyInFragment) { // Encryption is not supported yet for samples specified in the sample table. return null; } int sampleDescriptionIndex = castNonNull(fragment.header).sampleDescriptionIndex; @Nullable TrackEncryptionBox encryptionBox = fragment.trackEncryptionBox != null ? fragment.trackEncryptionBox : moovSampleTable.track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex); return encryptionBox != null && encryptionBox.isEncrypted ? encryptionBox : null; } } } ","metadataSampleTimeUs " "/* * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.source.ads; import static com.google.android.exoplayer2.C.DATA_TYPE_MEDIA; import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilPosition; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled; import static com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState; import static com.google.android.exoplayer2.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.SurfaceTexture; import android.os.Handler; import android.util.Pair; import android.view.Surface; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.robolectric.PlaybackOutput; import com.google.android.exoplayer2.robolectric.RobolectricUtil; import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.testutil.CapturingRenderersFactory; import com.google.android.exoplayer2.testutil.DumpFileAsserts; import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeMediaPeriod; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeSampleStream; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; /** Unit test for {@link ServerSideAdInsertionMediaSource}. */ @RunWith(AndroidJUnit4.class) public final class ServerSideAdInsertionMediaSourceTest { @Rule public ShadowMediaCodecConfig mediaCodecConfig = ShadowMediaCodecConfig.forAllSupportedMimeTypes(); private static final String TEST_ASSET = ""asset:///media/mp4/sample.mp4""; private static final String TEST_ASSET_DUMP = ""playbackdumps/mp4/ssai-sample.mp4.dump""; @Test public void timeline_vodSinglePeriod_containsAdsDefinedInAdPlaybackState() throws Exception { FakeTimeline wrappedTimeline = new FakeTimeline( new FakeTimeline.TimelineWindowDefinition( /* periodCount= */ 1, /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false, /* isPlaceholder= */ false, /* durationUs= */ 10_000_000, /* defaultPositionUs= */ 3_000_000, /* windowOffsetInFirstPeriodUs= */ 42_000_000L, AdPlaybackState.NONE)); ServerSideAdInsertionMediaSource mediaSource = new ServerSideAdInsertionMediaSource( new FakeMediaSource(wrappedTimeline), /* adPlaybackStateUpdater= */ null); // Test with one ad group before the window, and the window starting within the second ad group. AdPlaybackState adPlaybackState = new AdPlaybackState( /* adsId= */ new Object(), /* adGroupTimesUs...= */ 15_000_000, 41_500_000, 42_200_000) .withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true) .withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true) .withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2) .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1) .withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ 500_000) .withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 300_000, 100_000) .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 400_000) .withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100_000) .withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 400_000) .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 200_000); AtomicReference timelineReference = new AtomicReference<>(); mediaSource.setAdPlaybackStates( ImmutableMap.of(new Pair<>(0, 0), adPlaybackState), wrappedTimeline); mediaSource.prepareSource( (source, timeline) -> timelineReference.set(timeline), /* mediaTransferListener= */ null, PlayerId.UNSET); runMainLooperUntil(() -> timelineReference.get() != null); Timeline timeline = timelineReference.get(); assertThat(timeline.getPeriodCount()).isEqualTo(1); Timeline.Period period = timeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period()); assertThat(period.getAdGroupCount()).isEqualTo(3); assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 0)).isEqualTo(1); assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 1)).isEqualTo(2); assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 2)).isEqualTo(1); assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 0)).isEqualTo(15_000_000); assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 1)).isEqualTo(41_500_000); assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 2)).isEqualTo(42_200_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)) .isEqualTo(500_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0)) .isEqualTo(300_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1)) .isEqualTo(100_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0)) .isEqualTo(400_000); assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 0)).isEqualTo(100_000); assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 1)).isEqualTo(400_000); assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 2)).isEqualTo(200_000); // windowDurationUs + windowOffsetInFirstPeriodUs - sum(adDurations) + sum(contentResumeOffsets) assertThat(period.getDurationUs()).isEqualTo(51_400_000); // positionInWindowUs + sum(adDurationsBeforeWindow) - sum(contentResumeOffsetsBeforeWindow) assertThat(period.getPositionInWindowUs()).isEqualTo(-41_600_000); Timeline.Window window = timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()); assertThat(window.positionInFirstPeriodUs).isEqualTo(41_600_000); // windowDurationUs - sum(adDurationsInWindow) + sum(applicableContentResumeOffsetUs) assertThat(window.durationUs).isEqualTo(9_800_000); } @Test public void createPeriod_unpreparedAdMediaPeriodImplReplacesContentPeriod_adPeriodNotSelected() throws Exception { DefaultAllocator allocator = new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024); MediaPeriod.Callback callback = new MediaPeriod.Callback() { @Override public void onPrepared(MediaPeriod mediaPeriod) {} @Override public void onContinueLoadingRequested(MediaPeriod source) {} }; AdPlaybackState adPlaybackState = new AdPlaybackState(""adsId"").withLivePostrollPlaceholderAppended(); FakeTimeline wrappedTimeline = new FakeTimeline( new FakeTimeline.TimelineWindowDefinition( /* periodCount= */ 1, /* id= */ 0, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false, /* isPlaceholder= */ false, /* durationUs= */ 10_000_000L, /* defaultPositionUs= */ 3_000_000L, /* windowOffsetInFirstPeriodUs= */ 0L, AdPlaybackState.NONE)); ServerSideAdInsertionMediaSource mediaSource = new ServerSideAdInsertionMediaSource( new FakeMediaSource(wrappedTimeline), /* adPlaybackStateUpdater= */ null); AtomicReference timelineReference = new AtomicReference<>(); AtomicReference mediaPeriodIdReference = new AtomicReference<>(); mediaSource.setAdPlaybackStates( ImmutableMap.of(new Pair<>(0, 0), adPlaybackState), wrappedTimeline); mediaSource.addEventListener( new Handler(Util.getCurrentOrMainLooper()), new MediaSourceEventListener() { @Override public void onDownstreamFormatChanged( int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) { mediaPeriodIdReference.set(mediaPeriodId); } }); mediaSource.prepareSource( (source, timeline) -> timelineReference.set(timeline), /* mediaTransferListener= */ null, PlayerId.UNSET); runMainLooperUntil(() -> timelineReference.get() != null); Timeline firstTimeline = timelineReference.get(); MediaSource.MediaPeriodId mediaPeriodId1 = new MediaSource.MediaPeriodId( new Pair<>(0, 0), /* windowSequenceNumber= */ 0L, /* nextAdGroupIndex= */ 0); MediaSource.MediaPeriodId mediaPeriodId2 = new MediaSource.MediaPeriodId( new Pair<>(0, 0), /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* windowSequenceNumber= */ 0L); // Create and prepare the first period. MediaPeriod mediaPeriod1 = mediaSource.createPeriod(mediaPeriodId1, allocator, /* startPositionUs= */ 0L); mediaPeriod1.prepare(callback, /* positionUs= */ 0L); // Update the playback state to turn the content period into an ad period. adPlaybackState = adPlaybackState .withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 0L) .withIsServerSideInserted(/* adGroupIndex= */ 0, true) .withAdCount(/* adGroupIndex= */ 0, 1) .withContentResumeOffsetUs(/* adGroupIndex= */ 0, 10_000_000L) .withAdDurationsUs(/* adGroupIndex= */ 0, 10_000_000L); mediaSource.setAdPlaybackStates( ImmutableMap.of(new Pair<>(0, 0), adPlaybackState), wrappedTimeline); runMainLooperUntil(() -> !timelineReference.get().equals(firstTimeline)); // Create the second period that is tied to the same SharedMediaPeriod internally. mediaSource.createPeriod(mediaPeriodId2, allocator, /* startPositionUs= */ 0L); // Issue a onDownstreamFormatChanged event for mediaPeriodId1. The SharedPeriod selects in // `getMediaPeriodForEvent` from the following `MediaPeriodImpl`s for // MediaLoadData.mediaStartTimeMs=0 to 10_000_00. // [ // isPrepared: true, // startPositionMs: 0, // endPositionMs: 0, // adGroupIndex: -1, // adIndexInAdGroup: -1, // nextAdGroupIndex: 0, // ], // [ // isPrepared: false, // startPositionMs: 0, // endPositionMs: 10_000_000, // adGroupIndex: 0, // adIndexInAdGroup: 0, // nextAdGroupIndex: -1, // ] MediaLoadData mediaLoadData = new MediaLoadData( /* dataType= */ DATA_TYPE_MEDIA, C.TRACK_TYPE_VIDEO, new Format.Builder().build(), C.SELECTION_REASON_INITIAL, /* trackSelectionData= */ null, /* mediaStartTimeMs= */ 123L, /* mediaEndTimeMs= */ 10_000_000L); mediaSource.onDownstreamFormatChanged(/* windowIndex= */ 0, mediaPeriodId1, mediaLoadData); runMainLooperUntil( () -> mediaPeriodId1.equals(mediaPeriodIdReference.get()), /* timeoutMs= */ 500L, Clock.DEFAULT); assertThat(mediaPeriodIdReference.get()).isEqualTo(mediaPeriodId1); } @Test public void timeline_liveSinglePeriodWithUnsetPeriodDuration_containsAdsDefinedInAdPlaybackState() throws Exception { Timeline wrappedTimeline = new SinglePeriodTimeline( /* periodDurationUs= */ C.TIME_UNSET, /* windowDurationUs= */ 10_000_000, /* windowPositionInPeriodUs= */ 42_000_000L, /* windowDefaultStartPositionUs= */ 3_000_000, /* isSeekable= */ true, /* isDynamic= */ true, /* useLiveConfiguration= */ true, /* manifest= */ null, /* mediaItem= */ MediaItem.EMPTY); ServerSideAdInsertionMediaSource mediaSource = new ServerSideAdInsertionMediaSource( new FakeMediaSource(wrappedTimeline), /* adPlaybackStateUpdater= */ null); // Test with one ad group before the window, and the window starting within the second ad group. AdPlaybackState adPlaybackState = new AdPlaybackState( /* adsId= */ new Object(), /* adGroupTimesUs...= */ 15_000_000, 41_500_000, 42_200_000) .withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true) .withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true) .withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2) .withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1) .withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ 500_000) .withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 300_000, 100_000) .withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs...= */ 400_000) .withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100_000) .withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 400_000) .withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 200_000); AtomicReference timelineReference = new AtomicReference<>(); mediaSource.setAdPlaybackStates( ImmutableMap.of( wrappedTimeline.getPeriod( /* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true) .uid, adPlaybackState), wrappedTimeline); mediaSource.prepareSource( (source, timeline) -> timelineReference.set(timeline), /* mediaTransferListener= */ null, PlayerId.UNSET); runMainLooperUntil(() -> timelineReference.get() != null); Timeline timeline = timelineReference.get(); assertThat(timeline.getPeriodCount()).isEqualTo(1); Timeline.Period period = timeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period()); assertThat(period.getAdGroupCount()).isEqualTo(3); assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 0)).isEqualTo(1); assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 1)).isEqualTo(2); assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 2)).isEqualTo(1); assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 0)).isEqualTo(15_000_000); assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 1)).isEqualTo(41_500_000); assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 2)).isEqualTo(42_200_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)) .isEqualTo(500_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0)) .isEqualTo(300_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1)) .isEqualTo(100_000); assertThat(period.getAdDurationUs(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0)) .isEqualTo(400_000); assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 0)).isEqualTo(100_000); assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 1)).isEqualTo(400_000); assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 2)).isEqualTo(200_000); assertThat(period.getDurationUs()).isEqualTo(C.TIME_UNSET); // positionInWindowUs + sum(adDurationsBeforeWindow) - sum(contentResumeOffsetsBeforeWindow) assertThat(period.getPositionInWindowUs()).isEqualTo(-41_600_000); Timeline.Window window = timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()); assertThat(window.positionInFirstPeriodUs).isEqualTo(41_600_000); // windowDurationUs - sum(adDurationsInWindow) + sum(applicableContentResumeOffsetUs) assertThat(window.durationUs).isEqualTo(9_800_000); } @Test public void timeline_missingAdPlaybackStateByPeriodUid_isAssertedAndThrows() { FakeMediaSource contentSource = new FakeMediaSource(); ServerSideAdInsertionMediaSource mediaSource = new ServerSideAdInsertionMediaSource(contentSource, /* adPlaybackStateUpdater= */ null); // The map of adPlaybackStates does not contain a valid period UID as key. mediaSource.setAdPlaybackStates( ImmutableMap.of(new Object(), new AdPlaybackState(/* adsId= */ new Object())), contentSource.getInitialTimeline()); Assert.assertThrows( IllegalStateException.class, () -> mediaSource.prepareSource( (source, timeline) -> { /* Do nothing. */ }, /* mediaTransferListener= */ null, PlayerId.UNSET)); } @Test public void playbackWithPredefinedAds_playsSuccessfulWithoutRendererResets() throws Exception { Context context = ApplicationProvider.getApplicationContext(); CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context); ExoPlayer player = new ExoPlayer.Builder(context, renderersFactory) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory); AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object()); adPlaybackState = addAdGroupToAdPlaybackState( adPlaybackState, /* fromPositionUs= */ 0, /* contentResumeOffsetUs= */ 0, /* adDurationsUs...= */ 200_000); adPlaybackState = addAdGroupToAdPlaybackState( adPlaybackState, /* fromPositionUs= */ 400_000, /* contentResumeOffsetUs= */ 1_000_000, /* adDurationsUs...= */ 300_000); AdPlaybackState firstAdPlaybackState = addAdGroupToAdPlaybackState( adPlaybackState, /* fromPositionUs= */ 900_000, /* contentResumeOffsetUs= */ 0, /* adDurationsUs...= */ 100_000); AtomicReference mediaSourceRef = new AtomicReference<>(); mediaSourceRef.set( new ServerSideAdInsertionMediaSource( new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)), contentTimeline -> { Object periodUid = checkNotNull( contentTimeline.getPeriod( /* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true) .uid); mediaSourceRef .get() .setAdPlaybackStates( ImmutableMap.of(periodUid, firstAdPlaybackState), contentTimeline); return true; })); AnalyticsListener listener = mock(AnalyticsListener.class); player.addAnalyticsListener(listener); player.setMediaSource(mediaSourceRef.get()); player.prepare(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); player.release(); // Assert all samples have been played. DumpFileAsserts.assertOutput(context, playbackOutput, TEST_ASSET_DUMP); // Assert playback has been reported with ads: [ad0][content][ad1][content][ad2][content] // 6*2(audio+video) format changes, 5 discontinuities between parts. verify(listener, times(5)) .onPositionDiscontinuity( any(), any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); verify(listener, times(12)).onDownstreamFormatChanged(any(), any()); // Assert renderers played through without reset (=decoders have been enabled only once). verify(listener).onVideoEnabled(any(), any()); verify(listener).onAudioEnabled(any(), any()); // Assert playback progression was smooth (=no unexpected delays that cause audio to underrun) verify(listener, never()).onAudioUnderrun(any(), anyInt(), anyLong(), anyLong()); } @Test public void playbackWithNewlyInsertedAds_playsSuccessfulWithoutRendererResets() throws Exception { Context context = ApplicationProvider.getApplicationContext(); AtomicReference periodUid = new AtomicReference<>(); CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context); ExoPlayer player = new ExoPlayer.Builder(context, renderersFactory) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory); AdPlaybackState firstAdPlaybackState = addAdGroupToAdPlaybackState( new AdPlaybackState(/* adsId= */ new Object()), /* fromPositionUs= */ 900_000, /* contentResumeOffsetUs= */ 0, /* adDurationsUs...= */ 100_000); AtomicReference mediaSourceRef = new AtomicReference<>(); ArrayList contentTimelines = new ArrayList<>(); mediaSourceRef.set( new ServerSideAdInsertionMediaSource( new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)), /* adPlaybackStateUpdater= */ contentTimeline -> { periodUid.set( checkNotNull( contentTimeline.getPeriod( /* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true) .uid)); contentTimelines.add(contentTimeline); mediaSourceRef .get() .setAdPlaybackStates( ImmutableMap.of(periodUid.get(), firstAdPlaybackState), contentTimeline); return true; })); AnalyticsListener listener = mock(AnalyticsListener.class); player.addAnalyticsListener(listener); player.setMediaSource(mediaSourceRef.get()); player.prepare(); // Add ad at the current playback position during playback. runUntilPlaybackState(player, Player.STATE_READY); AdPlaybackState secondAdPlaybackState = addAdGroupToAdPlaybackState( firstAdPlaybackState, /* fromPositionUs= */ 0, /* contentResumeOffsetUs= */ 0, /* adDurationsUs...= */ 500_000); mediaSourceRef .get() .setAdPlaybackStates( ImmutableMap.of(periodUid.get(), secondAdPlaybackState), contentTimelines.get(1)); runUntilPendingCommandsAreFullyHandled(player); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); player.release(); // Assert all samples have been played. DumpFileAsserts.assertOutput(context, playbackOutput, TEST_ASSET_DUMP); assertThat(contentTimelines).hasSize(2); // Assert playback has been reported with ads: [content][ad0][content][ad1][content] // 5*2(audio+video) format changes, 4 discontinuities between parts. verify(listener, times(4)) .onPositionDiscontinuity( any(), any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); verify(listener, times(10)).onDownstreamFormatChanged(any(), any()); // Assert renderers played through without reset (=decoders have been enabled only once). verify(listener).onVideoEnabled(any(), any()); verify(listener).onAudioEnabled(any(), any()); // Assert playback progression was smooth (=no unexpected delays that cause audio to underrun) verify(listener, never()).onAudioUnderrun(any(), anyInt(), anyLong(), anyLong()); } @Test public void playbackWithAdditionalAdsInAdGroup_playsSuccessfulWithoutRendererResets() throws Exception { Context context = ApplicationProvider.getApplicationContext(); AtomicReference periodUid = new AtomicReference<>(); CapturingRenderersFactory renderersFactory = new CapturingRenderersFactory(context); ExoPlayer player = new ExoPlayer.Builder(context, renderersFactory) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) .build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); PlaybackOutput playbackOutput = PlaybackOutput.register(player, renderersFactory); AdPlaybackState firstAdPlaybackState = addAdGroupToAdPlaybackState( new AdPlaybackState(/* adsId= */ new Object()), /* fromPositionUs= */ 0, /* contentResumeOffsetUs= */ 0, /* adDurationsUs...= */ 500_000); AtomicReference mediaSourceRef = new AtomicReference<>(); ArrayList contentTimelines = new ArrayList<>(); mediaSourceRef.set( new ServerSideAdInsertionMediaSource( new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)), /* adPlaybackStateUpdater= */ contentTimeline -> { contentTimelines.add(contentTimeline); if (periodUid.get() == null) { periodUid.set( checkNotNull( contentTimeline.getPeriod( /* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true) .uid)); mediaSourceRef .get() .setAdPlaybackStates( ImmutableMap.of(periodUid.get(), firstAdPlaybackState), contentTimeline); } return true; })); AnalyticsListener listener = mock(AnalyticsListener.class); player.addAnalyticsListener(listener); player.setMediaSource(mediaSourceRef.get()); player.prepare(); // Wait until playback is ready with first ad and then replace by 3 ads. runUntilPlaybackState(player, Player.STATE_READY); AdPlaybackState secondAdPlaybackState = firstAdPlaybackState .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3) .withAdDurationsUs( /* adGroupIndex= */ 0, /* adDurationsUs...= */ 50_000, 250_000, 200_000); mediaSourceRef .get() .setAdPlaybackStates( ImmutableMap.of(periodUid.get(), secondAdPlaybackState), contentTimelines.get(1)); runUntilPendingCommandsAreFullyHandled(player); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); player.release(); // Assert all samples have been played. DumpFileAsserts.assertOutput(context, playbackOutput, TEST_ASSET_DUMP); assertThat(contentTimelines).hasSize(2); // Assert playback has been reported with ads: [ad0][ad1][ad2][content] // 4*2(audio+video) format changes, 3 discontinuities between parts. verify(listener, times(3)) .onPositionDiscontinuity( any(), any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); verify(listener, times(8)).onDownstreamFormatChanged(any(), any()); // Assert renderers played through without reset (=decoders have been enabled only once). verify(listener).onVideoEnabled(any(), any()); verify(listener).onAudioEnabled(any(), any()); // Assert playback progression was smooth (=no unexpected delays that cause audio to underrun) verify(listener, never()).onAudioUnderrun(any(), anyInt(), anyLong(), anyLong()); } @Test public void playbackWithSeek_isHandledCorrectly() throws Exception { Context context = ApplicationProvider.getApplicationContext(); ExoPlayer player = new ExoPlayer.Builder(context).setClock(new FakeClock(/* isAutoAdvancing= */ true)).build(); player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object()); adPlaybackState = addAdGroupToAdPlaybackState( adPlaybackState, /* fromPositionUs= */ 0, /* contentResumeOffsetUs= */ 0, /* adDurationsUs...= */ 100_000); adPlaybackState = addAdGroupToAdPlaybackState( adPlaybackState, /* fromPositionUs= */ 600_000, /* contentResumeOffsetUs= */ 1_000_000, /* adDurationsUs...= */ 100_000); AdPlaybackState firstAdPlaybackState = addAdGroupToAdPlaybackState( adPlaybackState, /* fromPositionUs= */ 900_000, /* contentResumeOffsetUs= */ 0, /* adDurationsUs...= */ 100_000); AtomicReference mediaSourceRef = new AtomicReference<>(); mediaSourceRef.set( new ServerSideAdInsertionMediaSource( new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)), /* adPlaybackStateUpdater= */ contentTimeline -> { Object periodUid = checkNotNull( contentTimeline.getPeriod( /* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true) .uid); mediaSourceRef .get() .setAdPlaybackStates( ImmutableMap.of(periodUid, firstAdPlaybackState), contentTimeline); return true; })); AnalyticsListener listener = mock(AnalyticsListener.class); player.addAnalyticsListener(listener); player.setMediaSource(mediaSourceRef.get()); player.prepare(); // Play to the first content part, then seek past the midroll. playUntilPosition(player, /* mediaItemIndex= */ 0, /* positionMs= */ 100); player.seekTo(/* positionMs= */ 1_600); runUntilPendingCommandsAreFullyHandled(player); long positionAfterSeekMs = player.getCurrentPosition(); long contentPositionAfterSeekMs = player.getContentPosition(); player.play(); runUntilPlaybackState(player, Player.STATE_ENDED); player.release(); // Assert playback has been reported with ads: [ad0][content] seek [ad1][content][ad2][content] // 6*2(audio+video) format changes, 4 auto-transitions between parts, 1 seek with adjustment. verify(listener, times(4)) .onPositionDiscontinuity( any(), any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION)); verify(listener, times(1)) .onPositionDiscontinuity(any(), any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK)); verify(listener, times(1)) .onPositionDiscontinuity( any(), any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT)); verify(listener, times(12)).onDownstreamFormatChanged(any(), any()); assertThat(contentPositionAfterSeekMs).isEqualTo(1_600); assertThat(positionAfterSeekMs).isEqualTo(0); // Beginning of second ad. // Assert renderers played through without reset, except for the seek. verify(listener, times(2)).onVideoEnabled(any(), any()); verify(listener, times(2)).onAudioEnabled(any(), any()); // Assert playback progression was smooth (=no unexpected delays that cause audio to underrun) verify(listener, never()).onAudioUnderrun(any(), anyInt(), anyLong(), anyLong()); } @Test public void serverSideAdInsertionSampleStream_withFastLoadingSourceAfterFirstRead_canBeReadFully() throws Exception { TrackGroup trackGroup = new TrackGroup(new Format.Builder().build()); // Set up MediaPeriod with no samples and only add samples after the first SampleStream read. FakeMediaPeriod mediaPeriod = new FakeMediaPeriod( new TrackGroupArray(trackGroup), new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), /* trackDataFactory= */ (format, mediaPeriodId) -> ImmutableList.of(), new MediaSourceEventListener.EventDispatcher() .withParameters( /* windowIndex= */ 0, new MediaSource.MediaPeriodId(/* periodUid= */ new Object())), DrmSessionManager.DRM_UNSUPPORTED, new DrmSessionEventListener.EventDispatcher(), /* deferOnPrepared= */ false) { @Override protected FakeSampleStream createSampleStream( Allocator allocator, @Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, Format [MASK] , List fakeSampleStreamItems) { return new FakeSampleStream( allocator, mediaSourceEventDispatcher, drmSessionManager, drmEventDispatcher, [MASK] , /* fakeSampleStreamItems= */ ImmutableList.of()) { private boolean addedSamples = false; @Override public int readData( FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { int result = super.readData(formatHolder, buffer, readFlags); if (!addedSamples) { append( ImmutableList.of( oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 200, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 400, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 600, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 800, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); writeData(/* startPositionUs= */ 0); addedSamples = true; } return result; } }; } }; FakeMediaSource mediaSource = new FakeMediaSource() { @Override protected MediaPeriod createMediaPeriod( MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, @Nullable TransferListener transferListener) { return mediaPeriod; } }; ServerSideAdInsertionMediaSource serverSideAdInsertionMediaSource = new ServerSideAdInsertionMediaSource(mediaSource, /* adPlaybackStateUpdater= */ null); Timeline timeline = new FakeTimeline(); Object periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0); serverSideAdInsertionMediaSource.setAdPlaybackStates( ImmutableMap.of(periodUid, new AdPlaybackState(/* adsId= */ new Object())), timeline); AtomicBoolean sourcePrepared = new AtomicBoolean(); serverSideAdInsertionMediaSource.prepareSource( (source, newTimeline) -> sourcePrepared.set(true), /* mediaTransferListener= */ null, PlayerId.UNSET); RobolectricUtil.runMainLooperUntil(sourcePrepared::get); MediaPeriod serverSideAdInsertionMediaPeriod = serverSideAdInsertionMediaSource.createPeriod( new MediaSource.MediaPeriodId(periodUid), /* allocator= */ null, /* startPositionUs= */ 0); AtomicBoolean periodPrepared = new AtomicBoolean(); serverSideAdInsertionMediaPeriod.prepare( new MediaPeriod.Callback() { @Override public void onPrepared(MediaPeriod mediaPeriod) { periodPrepared.set(true); } @Override public void onContinueLoadingRequested(MediaPeriod source) { serverSideAdInsertionMediaPeriod.continueLoading(/* positionUs= */ 0); } }, /* positionUs= */ 0); RobolectricUtil.runMainLooperUntil(periodPrepared::get); SampleStream[] sampleStreams = new SampleStream[1]; serverSideAdInsertionMediaPeriod.selectTracks( new ExoTrackSelection[] {new FixedTrackSelection(trackGroup, /* track= */ 0)}, /* mayRetainStreamFlags= */ new boolean[] {false}, sampleStreams, /* streamResetFlags= */ new boolean[] {true}, /* positionUs= */ 0); FormatHolder formatHolder = new FormatHolder(); DecoderInputBuffer buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); ArrayList readSamples = new ArrayList<>(); int result; do { result = sampleStreams[0].readData(formatHolder, buffer, /* readFlags= */ 0); if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) { readSamples.add(buffer.timeUs); } } while (result != C.RESULT_BUFFER_READ || !buffer.isEndOfStream()); assertThat(readSamples).containsExactly(0L, 200L, 400L, 600L, 800L).inOrder(); } } ","initialFormat " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.remoting.transport.netty4; import org.apache.dubbo.common.resource.GlobalResourceInitializer; import org.apache.dubbo.remoting.Constants; import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.DefaultThreadFactory; import java.util.concurrent.ThreadFactory; import static org.apache.dubbo.common.constants.CommonConstants.OS_LINUX_PREFIX; import static org.apache.dubbo.common.constants.CommonConstants.OS_NAME_KEY; import static org.apache.dubbo.remoting.Constants.NETTY_EPOLL_ENABLE_KEY; public class NettyEventLoopFactory { /** * netty client bootstrap */ public static final GlobalResourceInitializer NIO_EVENT_LOOP_GROUP = new GlobalResourceInitializer<>(() -> eventLoopGroup(Constants.DEFAULT_IO_THREADS, ""NettyClientWorker""), eventLoopGroup -> eventLoopGroup.shutdownGracefully() ); public static EventLoopGroup eventLoopGroup(int [MASK] , String threadFactoryName) { ThreadFactory threadFactory = new DefaultThreadFactory(threadFactoryName, true); return shouldEpoll() ? new EpollEventLoopGroup( [MASK] , threadFactory) : new NioEventLoopGroup( [MASK] , threadFactory); } public static Class socketChannelClass() { return shouldEpoll() ? EpollSocketChannel.class : NioSocketChannel.class; } public static Class serverSocketChannelClass() { return shouldEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class; } private static boolean shouldEpoll() { if (Boolean.parseBoolean(System.getProperty(NETTY_EPOLL_ENABLE_KEY, ""false""))) { String osName = System.getProperty(OS_NAME_KEY); return osName.toLowerCase().contains(OS_LINUX_PREFIX) && Epoll.isAvailable(); } return false; } } ","threads " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.configcenter.support.nacos; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; import org.apache.dubbo.common.config.configcenter.ConfigurationListener; import org.apache.dubbo.common.config.configcenter.DynamicConfiguration; import org.apache.dubbo.rpc.model.ApplicationModel; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit test for nacos config center support */ //FIXME: waiting for embedded Nacos suport, then we can open the switch. @Disabled(""https://github.com/alibaba/nacos/issues/1188"") class NacosDynamicConfigurationTest { private static final String SESSION_TIMEOUT_KEY = ""session""; private static NacosDynamicConfiguration config; /** * A test client to put data to Nacos server for testing purpose */ private static ConfigService nacosClient; @Test void testGetConfig() throws Exception { put(""org.apache.dubbo.nacos.testService.configurators"", ""hello""); Thread.sleep(200); put(""dubbo.properties"", ""test"", ""aaa=bbb""); Thread.sleep(200); put(""org.apache.dubbo.demo.DemoService:1.0.0.test:xxxx.configurators"", ""helloworld""); Thread.sleep(200); Assertions.assertEquals(""hello"", config.getConfig(""org.apache.dubbo.nacos.testService.configurators"", DynamicConfiguration.DEFAULT_GROUP)); Assertions.assertEquals(""aaa=bbb"", config.getConfig(""dubbo.properties"", ""test"")); Assertions.assertEquals(""helloworld"", config.getConfig(""org.apache.dubbo.demo.DemoService:1.0.0.test:xxxx.configurators"", DynamicConfiguration.DEFAULT_GROUP)); } @Test void testAddListener() throws Exception { CountDownLatch latch = new CountDownLatch(4); TestListener listener1 = new TestListener(latch); TestListener listener2 = new TestListener(latch); TestListener listener3 = new TestListener(latch); TestListener listener4 = new TestListener(latch); config.addListener(""AService.configurators"", listener1); config.addListener(""AService.configurators"", listener2); config.addListener(""testapp.tag-router"", listener3); config.addListener(""testapp.tag-router"", listener4); put(""AService.configurators"", ""new value1""); Thread.sleep(200); put(""testapp.tag-router"", ""new value2""); Thread.sleep(200); put(""testapp"", ""new value3""); Thread.sleep(5000); latch.await(); Assertions.assertEquals(1, listener1.getCount(""AService.configurators"")); Assertions.assertEquals(1, listener2.getCount(""AService.configurators"")); Assertions.assertEquals(1, listener3.getCount(""testapp.tag-router"")); Assertions.assertEquals(1, listener4.getCount(""testapp.tag-router"")); Assertions.assertEquals(""new value1"", listener1.getValue()); Assertions.assertEquals(""new value1"", listener2.getValue()); Assertions.assertEquals(""new value2"", listener3.getValue()); Assertions.assertEquals(""new value2"", listener4.getValue()); } // // @Test // public void testGetConfigKeys() { // // put(""key1"", ""a""); // put(""key2"", ""b""); // // SortedSet keys = config.getConfigKeys(DynamicConfiguration.DEFAULT_GROUP); // // Assertions.assertFalse(keys.isEmpty()); // // } private void put(String key, String value) { put(key, DynamicConfiguration.DEFAULT_GROUP, value); } private void put(String key, String group, String value) { try { nacosClient.publishConfig(key, group, value); } catch (Exception e) { System.out.println(""Error put value to nacos.""); } } @BeforeAll public static void setUp() { String urlForDubbo = ""nacos://"" + ""127.0.0.1:8848"" + ""/org.apache.dubbo.nacos.testService""; // timeout in 15 seconds. URL url = URL.valueOf(urlForDubbo) .addParameter(SESSION_TIMEOUT_KEY, 15000); config = new NacosDynamicConfiguration(url, ApplicationModel.defaultModel()); try { nacosClient = NacosFactory.createConfigService(""127.0.0.1:8848""); } catch (NacosException e) { e.printStackTrace(); } } @Test void testPublishConfig() { String key = ""user-service""; String group = ""org.apache.dubbo.service.UserService""; String content = ""test""; assertTrue(config.publishConfig(key, group, content)); assertEquals(""test"", config.getProperties(key, group)); } @AfterAll public static void tearDown() { } private class TestListener implements ConfigurationListener { private CountDownLatch latch; private String value; private Map countMap = new HashMap<>(); public TestListener(CountDownLatch latch) { this.latch = latch; } @Override public void process(ConfigChangedEvent [MASK] ) { System.out.println(this + "": "" + [MASK] ); Integer count = countMap.computeIfAbsent( [MASK] .getKey(), k -> 0); countMap.put( [MASK] .getKey(), ++count); value = [MASK] .getContent(); latch.countDown(); } public int getCount(String key) { return countMap.get(key); } public String getValue() { return value; } } } ","event " "// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.packages; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase; import java.util.Map; import net.starlark.java.eval.Dict; import net.starlark.java.eval.Starlark; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Test for {@link TargetUtils} */ @RunWith(JUnit4.class) public class TargetUtilsTest extends PackageLoadingTestCase { @Test public void getRuleLanguage() { assertThat(TargetUtils.getRuleLanguage(""java_binary"")).isEqualTo(""java""); assertThat(TargetUtils.getRuleLanguage(""foobar"")).isEqualTo(""foobar""); assertThat(TargetUtils.getRuleLanguage("""")).isEmpty(); } @Test public void testFilterByTag() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['tag1'])"", ""sh_binary(name = 'tag2', srcs=['sh.sh'], tags=['tag2'])"", ""sh_binary(name = 'tag1b', srcs=['sh.sh'], tags=['tag1'])""); Target tag1 = getTarget(""//tests:tag1""); Target tag2 = getTarget(""//tests:tag2""); Target tag1b = getTarget(""//tests:tag1b""); Predicate tagFilter = TargetUtils.tagFilter(Lists.newArrayList()); assertThat(tagFilter.apply(tag1)).isTrue(); assertThat(tagFilter.apply(tag2)).isTrue(); assertThat(tagFilter.apply(tag1b)).isTrue(); tagFilter = TargetUtils.tagFilter(Lists.newArrayList(""tag1"", ""tag2"")); assertThat(tagFilter.apply(tag1)).isTrue(); assertThat(tagFilter.apply(tag2)).isTrue(); assertThat(tagFilter.apply(tag1b)).isTrue(); tagFilter = TargetUtils.tagFilter(Lists.newArrayList(""tag1"")); assertThat(tagFilter.apply(tag1)).isTrue(); assertThat(tagFilter.apply(tag2)).isFalse(); assertThat(tagFilter.apply(tag1b)).isTrue(); tagFilter = TargetUtils.tagFilter(Lists.newArrayList(""-tag2"")); assertThat(tagFilter.apply(tag1)).isTrue(); assertThat(tagFilter.apply(tag2)).isFalse(); assertThat(tagFilter.apply(tag1b)).isTrue(); // Applying same tag as positive and negative filter produces an empty // result because the negative filter is applied first and positive filter will // not match anything. tagFilter = TargetUtils.tagFilter(Lists.newArrayList(""tag2"", ""-tag2"")); assertThat(tagFilter.apply(tag1)).isFalse(); assertThat(tagFilter.apply(tag2)).isFalse(); assertThat(tagFilter.apply(tag1b)).isFalse(); tagFilter = TargetUtils.tagFilter(Lists.newArrayList(""tag2"", ""-tag1"")); assertThat(tagFilter.apply(tag1)).isFalse(); assertThat(tagFilter.apply(tag2)).isTrue(); assertThat(tagFilter.apply(tag1b)).isFalse(); } @Test public void testExecutionInfo() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])"", ""sh_binary(name = 'tag2', srcs=['sh.sh'], tags=['disable-local-prefetch'])"", ""sh_binary(name = 'tag1b', srcs=['sh.sh'], tags=['local', 'cpu:4'])""); Rule tag1 = (Rule) getTarget(""//tests:tag1""); Rule tag2 = (Rule) getTarget(""//tests:tag2""); Rule tag1b = (Rule) getTarget(""//tests:tag1b""); Map execInfo = TargetUtils.getExecutionInfo(tag1); assertThat(execInfo).containsExactly(""supports-workers"", """", ""no-cache"", """"); execInfo = TargetUtils.getExecutionInfo(tag2); assertThat(execInfo).containsExactly(""disable-local-prefetch"", """"); execInfo = TargetUtils.getExecutionInfo(tag1b); assertThat(execInfo).containsExactly(""local"", """", ""cpu:4"", """"); } @Test public void testExecutionInfo_withPrefixSupports() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'with-prefix-supports', srcs=['sh.sh'], tags=['supports-workers',"" + "" 'supports-whatever', 'my-tag'])""); Rule withSupportsPrefix = (Rule) getTarget(""//tests:with-prefix-supports""); Map execInfo = TargetUtils.getExecutionInfo(withSupportsPrefix); assertThat(execInfo).containsExactly(""supports-whatever"", """", ""supports-workers"", """"); } @Test public void testExecutionInfo_withPrefixDisable() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'with-prefix-disable', srcs=['sh.sh'], tags=['disable-local-prefetch',"" + "" 'disable-something-else', 'another-tag'])""); Rule [MASK] = (Rule) getTarget(""//tests:with-prefix-disable""); Map execInfo = TargetUtils.getExecutionInfo( [MASK] ); assertThat(execInfo) .containsExactly(""disable-local-prefetch"", """", ""disable-something-else"", """"); } @Test public void testExecutionInfo_withPrefixNo() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'with-prefix-no', srcs=['sh.sh'], tags=['no-remote-imaginary-flag',"" + "" 'no-sandbox', 'unknown'])""); Rule withNoPrefix = (Rule) getTarget(""//tests:with-prefix-no""); Map execInfo = TargetUtils.getExecutionInfo(withNoPrefix); assertThat(execInfo).containsExactly(""no-remote-imaginary-flag"", """", ""no-sandbox"", """"); } @Test public void testExecutionInfo_withPrefixRequires() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'with-prefix-requires', srcs=['sh.sh'], tags=['requires-network',"" + "" 'requires-sunlight', 'test-only'])""); Rule withRequiresPrefix = (Rule) getTarget(""//tests:with-prefix-requires""); Map execInfo = TargetUtils.getExecutionInfo(withRequiresPrefix); assertThat(execInfo).containsExactly(""requires-network"", """", ""requires-sunlight"", """"); } @Test public void testExecutionInfo_withPrefixBlock() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'with-prefix-block', srcs=['sh.sh'], tags=['block-some-feature',"" + "" 'block-network', 'wrong-tag'])""); Rule withBlockPrefix = (Rule) getTarget(""//tests:with-prefix-block""); Map execInfo = TargetUtils.getExecutionInfo(withBlockPrefix); assertThat(execInfo).containsExactly(""block-network"", """", ""block-some-feature"", """"); } @Test public void testExecutionInfo_withPrefixCpu() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'with-prefix-cpu', srcs=['sh.sh'], tags=['cpu:123', 'wrong-tag'])""); Rule withCpuPrefix = (Rule) getTarget(""//tests:with-prefix-cpu""); Map execInfo = TargetUtils.getExecutionInfo(withCpuPrefix); assertThat(execInfo).containsExactly(""cpu:123"", """"); } @Test public void testExecutionInfo_withLocalTag() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'with-local-tag', srcs=['sh.sh'], tags=['local', 'some-tag'])""); Rule withLocal = (Rule) getTarget(""//tests:with-local-tag""); Map execInfo = TargetUtils.getExecutionInfo(withLocal); assertThat(execInfo).containsExactly(""local"", """"); } @Test public void testFilteredExecutionInfo_fromUncheckedExecRequirements() throws Exception { scratch.file(""tests/BUILD"", ""sh_binary(name = 'no-tag', srcs=['sh.sh'])""); Rule noTag = (Rule) getTarget(""//tests:no-tag""); Map execInfo = TargetUtils.getFilteredExecutionInfo( Dict.builder().put(""supports-worker"", ""1"").buildImmutable(), noTag, /* allowTagsPropagation */ true); assertThat(execInfo).containsExactly(""supports-worker"", ""1""); execInfo = TargetUtils.getFilteredExecutionInfo( Dict.builder() .put(""some-custom-tag"", ""1"") .put(""no-cache"", ""1"") .buildImmutable(), noTag, /* allowTagsPropagation */ true); assertThat(execInfo).containsExactly(""no-cache"", ""1""); } @Test public void testFilteredExecutionInfo_fromUncheckedExecRequirements_withWorkerKeyMnemonic() throws Exception { scratch.file(""tests/BUILD"", ""sh_binary(name = 'no-tag', srcs=['sh.sh'])""); Rule noTag = (Rule) getTarget(""//tests:no-tag""); Map execInfo = TargetUtils.getFilteredExecutionInfo( Dict.builder() .put(""supports-workers"", ""1"") .put(""worker-key-mnemonic"", ""MyMnemonic"") .buildImmutable(), noTag, /* allowTagsPropagation */ true); assertThat(execInfo) .containsExactly(""supports-workers"", ""1"", ""worker-key-mnemonic"", ""MyMnemonic""); } @Test public void testFilteredExecutionInfo() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])""); Rule tag1 = (Rule) getTarget(""//tests:tag1""); Dict executionRequirementsUnchecked = Dict.builder().put(""no-remote"", ""1"").buildImmutable(); Map execInfo = TargetUtils.getFilteredExecutionInfo( executionRequirementsUnchecked, tag1, /* allowTagsPropagation */ true); assertThat(execInfo).containsExactly(""no-cache"", """", ""supports-workers"", """", ""no-remote"", ""1""); } @Test public void testFilteredExecutionInfo_withDuplicateTags() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])""); Rule tag1 = (Rule) getTarget(""//tests:tag1""); Dict executionRequirementsUnchecked = Dict.builder().put(""no-cache"", ""1"").buildImmutable(); Map execInfo = TargetUtils.getFilteredExecutionInfo( executionRequirementsUnchecked, tag1, /* allowTagsPropagation */ true); assertThat(execInfo).containsExactly(""no-cache"", ""1"", ""supports-workers"", """"); } @Test public void testFilteredExecutionInfo_withNullUncheckedExecRequirements() throws Exception { scratch.file( ""tests/BUILD"", ""sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])""); Rule tag1 = (Rule) getTarget(""//tests:tag1""); Map execInfo = TargetUtils.getFilteredExecutionInfo(null, tag1, /* allowTagsPropagation */ true); assertThat(execInfo).containsExactly(""no-cache"", """", ""supports-workers"", """"); execInfo = TargetUtils.getFilteredExecutionInfo(Starlark.NONE, tag1, /* allowTagsPropagation */ true); assertThat(execInfo).containsExactly(""no-cache"", """", ""supports-workers"", """"); } @Test public void testFilteredExecutionInfo_whenIncompatibleFlagDisabled() throws Exception { // when --incompatible_allow_tags_propagation=false scratch.file( ""tests/BUILD"", ""sh_binary(name = 'tag1', srcs=['sh.sh'], tags=['supports-workers', 'no-cache'])""); Rule tag1 = (Rule) getTarget(""//tests:tag1""); Dict executionRequirementsUnchecked = Dict.builder().put(""no-remote"", ""1"").buildImmutable(); Map execInfo = TargetUtils.getFilteredExecutionInfo( executionRequirementsUnchecked, tag1, /* allowTagsPropagation */ false); assertThat(execInfo).containsExactly(""no-remote"", ""1""); } @Test public void testExecutionInfoMisc() throws Exception { // Migrated from a removed test class that was focused on top-level build configuration. // TODO(anyone): remove tests here that are redundant w.r.t. the other tests in this file. scratch.file( ""x/BUILD"", ""cc_test(name = 'y',"", "" srcs = ['a'],"", "" size = 'small',"", "" tags = ['manual','local','exclusive'])"", ""cc_test(name = 'z',"", "" srcs = ['a'],"", "" size = 'small',"", "" tags = ['othertag', 'requires-feature2'])"", ""cc_test(name = 'k',"", "" srcs = ['a'],"", "" size = 'small',"", "" tags = ['requires-feature1'])"", ""cc_test(name = 'exclusive_if_local',"", "" srcs = ['a'],"", "" size = 'small',"", "" tags = ['exclusive-if-local'])"", ""cc_test(name = 'exclusive_only',"", "" srcs = ['a'],"", "" size = 'small',"", "" tags = ['exclusive'])"", ""test_suite(name = 'ts',"", "" tests = ['z'])"", ""cc_binary(name = 'x',"", "" srcs = ['a', 'b', 'c'],"", "" defines = ['-Da', '-Db'])"", ""cc_binary(name = 'lib1',"", "" srcs = ['a', 'b', 'c'],"", "" linkshared = 1)"", ""genrule(name = 'gen1',"", "" srcs = [],"", "" outs = ['t1', 't2'],"", "" cmd = 'my cmd')"", ""genrule(name = 'gen2',"", "" srcs = ['liba.so'],"", "" outs = ['libnewa.so'],"", "" cmd = 'my cmd')""); Rule x = (Rule) getTarget(""//x:x""); assertThat(TargetUtils.isTestRule(x)).isFalse(); Rule ts = (Rule) getTarget(""//x:ts""); assertThat(TargetUtils.isTestRule(ts)).isFalse(); assertThat(TargetUtils.isTestOrTestSuiteRule(ts)).isTrue(); Rule z = (Rule) getTarget(""//x:z""); assertThat(TargetUtils.isTestRule(z)).isTrue(); assertThat(TargetUtils.isTestOrTestSuiteRule(z)).isTrue(); assertThat(TargetUtils.isExclusiveTestRule(z)).isFalse(); assertThat(TargetUtils.isExclusiveIfLocalTestRule(z)).isFalse(); assertThat(TargetUtils.isLocalTestRule(z)).isFalse(); assertThat(TargetUtils.hasManualTag(z)).isFalse(); assertThat(TargetUtils.getExecutionInfo(z)).doesNotContainKey(""requires-feature1""); assertThat(TargetUtils.getExecutionInfo(z)).containsKey(""requires-feature2""); Rule k = (Rule) getTarget(""//x:k""); assertThat(TargetUtils.isTestRule(k)).isTrue(); assertThat(TargetUtils.isTestOrTestSuiteRule(k)).isTrue(); assertThat(TargetUtils.isExclusiveTestRule(k)).isFalse(); assertThat(TargetUtils.isExclusiveIfLocalTestRule(k)).isFalse(); assertThat(TargetUtils.isLocalTestRule(k)).isFalse(); assertThat(TargetUtils.hasManualTag(k)).isFalse(); assertThat(TargetUtils.getExecutionInfo(k)).containsKey(""requires-feature1""); assertThat(TargetUtils.getExecutionInfo(k)).doesNotContainKey(""requires-feature2""); Rule y = (Rule) getTarget(""//x:y""); assertThat(TargetUtils.isTestRule(y)).isTrue(); assertThat(TargetUtils.isTestOrTestSuiteRule(y)).isTrue(); assertThat(TargetUtils.isExclusiveTestRule(y)).isTrue(); assertThat(TargetUtils.isExclusiveIfLocalTestRule(y)).isFalse(); assertThat(TargetUtils.isLocalTestRule(y)).isTrue(); assertThat(TargetUtils.hasManualTag(y)).isTrue(); assertThat(TargetUtils.getExecutionInfo(y)).doesNotContainKey(""requires-feature1""); assertThat(TargetUtils.getExecutionInfo(y)).doesNotContainKey(""requires-feature2""); Rule exclusiveIfRunLocally = (Rule) getTarget(""//x:exclusive_if_local""); assertThat(TargetUtils.isExclusiveIfLocalTestRule(exclusiveIfRunLocally)).isTrue(); assertThat(TargetUtils.isLocalTestRule(exclusiveIfRunLocally)).isFalse(); assertThat(TargetUtils.isExclusiveTestRule(exclusiveIfRunLocally)).isFalse(); Rule exclusive = (Rule) getTarget(""//x:exclusive_only""); assertThat(TargetUtils.isExclusiveTestRule(exclusive)).isTrue(); assertThat(TargetUtils.isLocalTestRule(exclusive)).isFalse(); // LOCAL tag gets added later. assertThat(TargetUtils.isExclusiveIfLocalTestRule(exclusive)).isFalse(); } } ","withDisablePrefix " "/* * Copyright 2017 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.ssl; import io.netty.util.internal.ObjectUtil; public abstract class SslCompletionEvent { private final Throwable [MASK] ; SslCompletionEvent() { [MASK] = null; } SslCompletionEvent(Throwable [MASK] ) { this. [MASK] = ObjectUtil.checkNotNull( [MASK] , "" [MASK] ""); } /** * Return {@code true} if the completion was successful */ public final boolean isSuccess() { return [MASK] == null; } /** * Return the {@link Throwable} if {@link #isSuccess()} returns {@code false} * and so the completion failed. */ public final Throwable [MASK] () { return [MASK] ; } @Override public String toString() { final Throwable [MASK] = [MASK] (); return [MASK] == null? getClass().getSimpleName() + ""(SUCCESS)"" : getClass().getSimpleName() + '(' + [MASK] + ')'; } } ","cause " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.tests; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.tests.utils.GdxTest; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.PerformanceCounter; import java.util.Comparator; /** For testing and benchmarking of gdx.utils.Select and its associated algorithms/classes * @author Jon Renner */ public class SelectTest extends GdxTest { static PerformanceCounter perf = new PerformanceCounter(""bench""); static boolean verify; // verify and report the results of each selection private static boolean quiet; @Override public void create () { int n = 100; player = createDummies(n); enemy = createDummies(n); int runs = 100; // run correctness first to warm up the JIT and other black magic quiet = true; allRandom(); print(""VERIFY CORRECTNESS FIND LOWEST RANKED""); correctnessTest(runs, 1); print(""VERIFY CORRECTNESS FIND MIDDLE RANKED""); correctnessTest(runs, enemy.size / 2); print(""VERIFY CORRECTNESS FIND HIGHEST RANKED""); correctnessTest(runs, enemy.size); runs = 1000; quiet = true; print(""BENCHMARK FIND LOWEST RANKED""); performanceTest(runs, 1); print(""BENCHMARK FIND MIDDLE RANKED""); performanceTest(runs, enemy.size / 2); print(""BENCHMARK FIND HIGHEST RANKED""); performanceTest(runs, enemy.size); print(""TEST CONSISTENCY FOR LOWEST RANKED""); consistencyTest(runs, 1); print(""TEST CONSISTENCY FOR MIDDLE RANKED""); consistencyTest(runs, enemy.size / 2); print(""TEST CONSISTENCY FOR HIGHEST RANKED""); consistencyTest(runs, enemy.size); // test that selectRanked and selectRankedIndex return the same print(""TEST selectRanked AND selectRankedIndex RETURN MATCHING RESULTS - LOWEST RANKED""); testValueMatchesIndex(runs, 1); print(""TEST selectRanked AND selectRankedIndex RETURN MATCHING RESULTS - MIDDLE RANKED""); testValueMatchesIndex(runs, enemy.size / 2); print(""TEST selectRanked AND selectRankedIndex RETURN MATCHING RESULTS - HIGHEST RANKED""); testValueMatchesIndex(runs, enemy.size); print(""ALL TESTS PASSED""); } public static void correctnessTest (int runs, int k) { String msg = String.format(""[%d runs with %dx%d dummy game units] - "", runs, player.size, enemy.size); verify = true; test(runs, k); print(msg + ""VERIFIED""); } public static void performanceTest (int runs, int k) { verify = false; test(runs, k); String msg = String.format(""[%d runs with %dx%d dummy game units] - "", runs, player.size, enemy.size); print(msg + String.format(""avg: %.5f, min/max: %.4f/%.4f, total time: %.3f (ms), made %d comparisons"", allPerf.time.min, allPerf.time.max, allPerf.time.average * 1000, allPerf.time.total * 1000, comparisonsMade)); } public static void consistencyTest (int runs, int k) { verify = false; Dummy test = player.get(0); Dummy lastFound = null; allRandom(); for (int i = 0; i < runs; i++) { Dummy found = test.getKthNearestEnemy(k); if (lastFound == null) { lastFound = found; } else { if (!(lastFound.equals(found))) { print(""CONSISTENCY TEST FAILED""); print(""lastFound: "" + lastFound); print(""justFound: "" + found); throw new GdxRuntimeException(""test failed""); } } } } public static void testValueMatchesIndex (int runs, int k) { verify = false; for (int i = 0; i < runs; i++) { allRandom(); player.shuffle(); enemy.shuffle(); originDummy = player.random(); int idx = enemy.selectRankedIndex(distComp, k); Dummy indexDummy = enemy.get(idx); Dummy valueDummy = enemy.selectRanked(distComp, k); if (!(indexDummy.equals(valueDummy))) { throw new GdxRuntimeException(""results of selectRankedIndex and selectRanked do not return the same object\n"" + ""selectRankedIndex -> "" + indexDummy + ""\n"" + ""selectRanked -> "" + valueDummy); } } } public static void test (int runs, int k) { // k = kth order statistic comparisonsMade = 0; perf.reset(); allPerf.reset(); allRandom(); enemy.shuffle(); player.shuffle(); for (int i = 0; i < runs; i++) { getKthNearestEnemy(quiet, k); } } public static void allRandom () { for (Dummy d : player) { d.setRandomPos(); } for (Dummy d : enemy) { d.setRandomPos(); } } private static PerformanceCounter allPerf = new PerformanceCounter(""all""); public static void getKthNearestEnemy (boolean silent, int k) { Dummy kthDummy = null; perf.reset(); allPerf.start(); for (Dummy d : player) { Dummy found = d.getKthNearestEnemy(k); } allPerf.stop(); allPerf.tick(); if (silent) return; print(String.format(""found nearest. min: %.4f, max: %.4f, avg: %.4f, total: %.3f ms"", perf.time.min * 1000, perf.time.max * 1000, perf.time.average * 1000, perf.time.total * 1000)); } public static void verifyCorrectness (Dummy d, int k) { enemy.sort(distComp); int idx = enemy.indexOf(d, true); // remember that k = min value = 0 position in the array, therefore k - 1 if (enemy.get(idx) != enemy.get(k - 1)) { System.out.println(""origin dummy: "" + originDummy); System.out.println(""TEST FAILURE: "" + ""idx: "" + idx + "" does not equal (k - 1): "" + (k - 1)); throw new GdxRuntimeException(""test failed""); } } static class Dummy { public Vector2 pos; public int id; public Dummy () { // set the position manually } @Override public boolean equals (Object obj) { if (!(obj instanceof Dummy)) { throw new GdxRuntimeException(""do not compare to anything but other Dummy objects""); } Dummy d = (Dummy)obj; // we only care about position/distance float epsilon = 0.0001f; float diff = Math.abs(d.pos.x - this.pos.x) + Math.abs(d.pos.y - this.pos.y); if (diff > epsilon) return false; return true; } public Dummy getKthNearestEnemy (int k) { perf.start(); originDummy = this; Dummy found = enemy.selectRanked(distComp, k); // print(this + "" found enemy: "" + found); perf.stop(); perf.tick(); if (verify) { verifyCorrectness(found, k); } return found; } public void setRandomPos () { float max = 100; this.pos.x = -max + MathUtils.random(max * 2); this.pos.y = -max + MathUtils.random(max * 2); float xShift = 100; if (player.contains(this, true)) { this.pos.x -= xShift; } else if (enemy.contains(this, true)) { this.pos.x += xShift; } else { throw new RuntimeException(""unhandled""); } } @Override public String toString () { return String.format(""Dummy at: %.2f, %.2f"", pos.x, pos.y); } } public static int nextID = 1; public static Array player; public static Array enemy; public static Array createDummies (int n) { float variance = 20; Array [MASK] = new Array(); for (int i = 0; i < n; i++) { Dummy d = new Dummy(); [MASK] .add(d); d.pos = new Vector2(); d.id = nextID++; } return [MASK] ; } static Dummy originDummy; static long comparisonsMade = 0; static Comparator distComp = new Comparator() { @Override public int compare (Dummy o1, Dummy o2) { comparisonsMade++; float d1 = originDummy.pos.dst2(o1.pos); float d2 = originDummy.pos.dst2(o2.pos); float diff = d1 - d2; if (diff < 0) return -1; if (diff > 0) return 1; return 0; } }; public static void print (Object... objs) { for (Object o : objs) { System.out.print(o); } System.out.println(); } } ","dummies " "/* * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the ""Classpath"" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.net.ssl; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.util.List; import java.util.function.BiFunction; /** * A class which enables secure communications using [MASK] such as * the Secure Sockets Layer (SSL) or * IETF RFC 2246 ""Transport * Layer Security"" (TLS) [MASK] , but is transport independent. *

* The secure communications modes include:

    * *
  • Integrity Protection. SSL/TLS protects against * modification of messages by an active wiretapper. * *
  • Authentication. In most modes, SSL/TLS provides * peer authentication. Servers are usually authenticated, and * clients may be authenticated as requested by servers. * *
  • Confidentiality (Privacy Protection). In most * modes, SSL/TLS encrypts data being sent between client and * server. This protects the confidentiality of data, so that * passive wiretappers won't see sensitive data such as financial * information or personal information of many kinds. * *
* * These kinds of protection are specified by a ""cipher suite"", which * is a combination of cryptographic algorithms used by a given SSL * connection. During the negotiation process, the two endpoints must * agree on a cipher suite that is available in both environments. If * there is no such suite in common, no SSL connection can be * established, and no data can be exchanged. *

* The cipher suite used is established by a negotiation process called * ""handshaking"". The goal of this process is to create or rejoin a * ""session"", which may protect many connections over time. After * handshaking has completed, you can access session attributes by * using the {@link #getSession()} method. *

* The SSLSocket class provides much of the same security * functionality, but all of the inbound and outbound data is * automatically transported using the underlying {@link * java.net.Socket Socket}, which by design uses a blocking model. * While this is appropriate for many applications, this model does not * provide the scalability required by large servers. *

* The primary distinction of an SSLEngine is that it * operates on inbound and outbound byte streams, independent of the * transport mechanism. It is the responsibility of the * SSLEngine user to arrange for reliable I/O transport to * the peer. By separating the SSL/TLS abstraction from the I/O * transport mechanism, the SSLEngine can be used for a * wide variety of I/O types, such as {@link * java.nio.channels.spi.AbstractSelectableChannel#configureBlocking(boolean) * non-blocking I/O (polling)}, {@link java.nio.channels.Selector * selectable non-blocking I/O}, {@link java.net.Socket Socket} and the * traditional Input/OutputStreams, local {@link java.nio.ByteBuffer * ByteBuffers} or byte arrays, future asynchronous * I/O models , and so on. *

* At a high level, the SSLEngine appears thus: * *

 *                   app data
 *
 *                |           ^
 *                |     |     |
 *                v     |     |
 *           +----+-----|-----+----+
 *           |          |          |
 *           |       SSL|Engine    |
 *   wrap()  |          |          |  unwrap()
 *           | OUTBOUND | INBOUND  |
 *           |          |          |
 *           +----+-----|-----+----+
 *                |     |     ^
 *                |     |     |
 *                v           |
 *
 *                   net data
 * 
* Application data (also known as plaintext or cleartext) is data which * is produced or consumed by an application. Its counterpart is * network data, which consists of either handshaking and/or ciphertext * (encrypted) data, and destined to be transported via an I/O * mechanism. Inbound data is data which has been received from the * peer, and outbound data is destined for the peer. *

* (In the context of an SSLEngine, the term ""handshake * data"" is taken to mean any data exchanged to establish and control a * secure connection. Handshake data includes the SSL/TLS messages * ""alert"", ""change_cipher_spec,"" and ""handshake."") *

* There are five distinct phases to an SSLEngine. * *

    *
  1. Creation - The SSLEngine has been created and * initialized, but has not yet been used. During this phase, an * application may set any SSLEngine-specific settings * (enabled cipher suites, whether the SSLEngine should * handshake in client or server mode, and so on). Once * handshaking has begun, though, any new settings (except * client/server mode, see below) will be used for * the next handshake. * *
  2. Initial Handshake - The initial handshake is a procedure by * which the two peers exchange communication parameters until an * SSLSession is established. Application data can not be sent during * this phase. * *
  3. Application Data - Once the communication parameters have * been established and the handshake is complete, application data * may flow through the SSLEngine. Outbound * application messages are encrypted and integrity protected, * and inbound messages reverse the process. * *
  4. Rehandshaking - Either side may request a renegotiation of * the session at any time during the Application Data phase. New * handshaking data can be intermixed among the application data. * Before starting the rehandshake phase, the application may * reset the SSL/TLS communication parameters such as the list of * enabled ciphersuites and whether to use client authentication, * but can not change between client/server modes. As before, once * handshaking has begun, any new SSLEngine * configuration settings will not be used until the next * handshake. * *
  5. Closure - When the connection is no longer needed, the * application should close the SSLEngine and should * send/receive any remaining messages to the peer before * closing the underlying transport mechanism. Once an engine is * closed, it is not reusable: a new SSLEngine must * be created. *
* An SSLEngine is created by calling {@link * SSLContext#createSSLEngine()} from an initialized * SSLContext. Any configuration * parameters should be set before making the first call to * wrap(), unwrap(), or * beginHandshake(). These methods all trigger the * initial handshake. *

* Data moves through the engine by calling {@link #wrap(ByteBuffer, * ByteBuffer) wrap()} or {@link #unwrap(ByteBuffer, ByteBuffer) * unwrap()} on outbound or inbound data, respectively. Depending on * the state of the SSLEngine, a wrap() call * may consume application data from the source buffer and may produce * network data in the destination buffer. The outbound data * may contain application and/or handshake data. A call to * unwrap() will examine the source buffer and may * advance the handshake if the data is handshaking information, or * may place application data in the destination buffer if the data * is application. The state of the underlying SSL/TLS algorithm * will determine when data is consumed and produced. *

* Calls to wrap() and unwrap() return an * SSLEngineResult which indicates the status of the * operation, and (optionally) how to interact with the engine to make * progress. *

* The SSLEngine produces/consumes complete SSL/TLS * packets only, and does not store application data internally between * calls to wrap()/unwrap(). Thus input and output * ByteBuffers must be sized appropriately to hold the * maximum record that can be produced. Calls to {@link * SSLSession#getPacketBufferSize()} and {@link * SSLSession#getApplicationBufferSize()} should be used to determine * the appropriate buffer sizes. The size of the outbound application * data buffer generally does not matter. If buffer conditions do not * allow for the proper consumption/production of data, the application * must determine (via {@link SSLEngineResult}) and correct the * problem, and then try the call again. *

* For example, unwrap() will return a {@link * SSLEngineResult.Status#BUFFER_OVERFLOW} result if the engine * determines that there is not enough destination buffer space available. * Applications should call {@link SSLSession#getApplicationBufferSize()} * and compare that value with the space available in the destination buffer, * enlarging the buffer if necessary. Similarly, if unwrap() * were to return a {@link SSLEngineResult.Status#BUFFER_UNDERFLOW}, the * application should call {@link SSLSession#getPacketBufferSize()} to ensure * that the source buffer has enough room to hold a record (enlarging if * necessary), and then obtain more inbound data. * *

{@code
 *   SSLEngineResult r = engine.unwrap(src, dst);
 *   switch (r.getStatus()) {
 *   BUFFER_OVERFLOW:
 *       // Could attempt to drain the dst buffer of any already obtained
 *       // data, but we'll just increase it to the size needed.
 *       int appSize = engine.getSession().getApplicationBufferSize();
 *       ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
 *       dst.flip();
 *       b.put(dst);
 *       dst = b;
 *       // retry the operation.
 *       break;
 *   BUFFER_UNDERFLOW:
 *       int netSize = engine.getSession().getPacketBufferSize();
 *       // Resize buffer if needed.
 *       if (netSize > dst.capacity()) {
 *           ByteBuffer b = ByteBuffer.allocate(netSize);
 *           src.flip();
 *           b.put(src);
 *           src = b;
 *       }
 *       // Obtain more inbound network data for src,
 *       // then retry the operation.
 *       break;
 *   // other cases: CLOSED, OK.
 *   }
 * }
* *

* Unlike SSLSocket, all methods of SSLEngine are * non-blocking. SSLEngine implementations may * require the results of tasks that may take an extended period of * time to complete, or may even block. For example, a TrustManager * may need to connect to a remote certificate validation service, * or a KeyManager might need to prompt a user to determine which * certificate to use as part of client authentication. Additionally, * creating cryptographic signatures and verifying them can be slow, * seemingly blocking. *

* For any operation which may potentially block, the * SSLEngine will create a {@link java.lang.Runnable} * delegated task. When SSLEngineResult indicates that a * delegated task result is needed, the application must call {@link * #getDelegatedTask()} to obtain an outstanding delegated task and * call its {@link java.lang.Runnable#run() run()} method (possibly using * a different thread depending on the compute strategy). The * application should continue obtaining delegated tasks until no more * exist, and try the original operation again. *

* At the end of a communication session, applications should properly * close the SSL/TLS link. The SSL/TLS [MASK] have closure handshake * messages, and these messages should be communicated to the peer * before releasing the SSLEngine and closing the * underlying transport mechanism. A close can be initiated by one of: * an SSLException, an inbound closure handshake message, or one of the * close methods. In all cases, closure handshake messages are * generated by the engine, and wrap() should be repeatedly * called until the resulting SSLEngineResult's status * returns ""CLOSED"", or {@link #isOutboundDone()} returns true. All * data obtained from the wrap() method should be sent to the * peer. *

* {@link #closeOutbound()} is used to signal the engine that the * application will not be sending any more data. *

* A peer will signal its intent to close by sending its own closure * handshake message. After this message has been received and * processed by the local SSLEngine's unwrap() * call, the application can detect the close by calling * unwrap() and looking for a SSLEngineResult * with status ""CLOSED"", or if {@link #isInboundDone()} returns true. * If for some reason the peer closes the communication link without * sending the proper SSL/TLS closure message, the application can * detect the end-of-stream and can signal the engine via {@link * #closeInbound()} that there will no more inbound messages to * process. Some applications might choose to require orderly shutdown * messages from a peer, in which case they can check that the closure * was generated by a handshake message and not by an end-of-stream * condition. *

* There are two groups of cipher suites which you will need to know * about when managing cipher suites: * *

    *
  • Supported cipher suites: all the suites which are * supported by the SSL implementation. This list is reported * using {@link #getSupportedCipherSuites()}. * *
  • Enabled cipher suites, which may be fewer than * the full set of supported suites. This group is set using the * {@link #setEnabledCipherSuites(String [])} method, and * queried using the {@link #getEnabledCipherSuites()} method. * Initially, a default set of cipher suites will be enabled on a * new engine that represents the minimum suggested * configuration. *
* * Implementation defaults require that only cipher suites which * authenticate servers and provide confidentiality be enabled by * default. Only if both sides explicitly agree to unauthenticated * and/or non-private (unencrypted) communications will such a * cipher suite be selected. *

* Each SSL/TLS connection must have one client and one server, thus * each endpoint must decide which role to assume. This choice determines * who begins the handshaking process as well as which type of messages * should be sent by each party. The method {@link * #setUseClientMode(boolean)} configures the mode. Once the initial * handshaking has started, an SSLEngine can not switch * between client and server modes, even when performing renegotiations. *

* Applications might choose to process delegated tasks in different * threads. When an SSLEngine * is created, the current {@link java.security.AccessControlContext} * is saved. All future delegated tasks will be processed using this * context: that is, all access control decisions will be made using the * context captured at engine creation. * *


* * Concurrency Notes: * There are two concurrency issues to be aware of: * *
    *
  1. The wrap() and unwrap() methods * may execute concurrently of each other. * *
  2. The SSL/TLS [MASK] employ ordered packets. * Applications must take care to ensure that generated packets * are delivered in sequence. If packets arrive * out-of-order, unexpected or fatal results may occur. *

    * For example: * *

     *              synchronized (outboundLock) {
     *                  sslEngine.wrap(src, dst);
     *                  outboundQueue.put(dst);
     *              }
     *      
    * * As a corollary, two threads must not attempt to call the same method * (either wrap() or unwrap()) concurrently, * because there is no way to guarantee the eventual packet ordering. *
* *

Default configuration for different Android versions

*

{@code SSLEngine} instances obtained from the default {@link SSLContext} are configured as * follows: * * * *

Protocols

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ProtocolSupported (API Levels)Enabled by default (API Levels)
SSLv31–251–22
TLSv11+1+
TLSv1.120+20+
TLSv1.220+20+
TLSv1.329+29+
* *

Cipher suites

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Cipher suiteSupported (API Levels)Enabled by default (API Levels)
SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA9-229-19
SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA9-229-19
SSL_DHE_DSS_WITH_DES_CBC_SHA9-229-19
SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA9-229-19
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA9-229-19
SSL_DHE_RSA_WITH_DES_CBC_SHA9-229-19
SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA9-22
SSL_DH_anon_EXPORT_WITH_RC4_40_MD59-22
SSL_DH_anon_WITH_3DES_EDE_CBC_SHA9-22
SSL_DH_anon_WITH_DES_CBC_SHA9-22
SSL_DH_anon_WITH_RC4_128_MD59-22
SSL_RSA_EXPORT_WITH_DES40_CBC_SHA9-229-19
SSL_RSA_EXPORT_WITH_RC4_40_MD59-229-19
SSL_RSA_WITH_3DES_EDE_CBC_SHA9+9-19
SSL_RSA_WITH_DES_CBC_SHA9-229-19
SSL_RSA_WITH_NULL_MD59-22
SSL_RSA_WITH_NULL_SHA9-22
SSL_RSA_WITH_RC4_128_MD59-259-19
SSL_RSA_WITH_RC4_128_SHA9-259-23
TLS_AES_128_GCM_SHA25629+29+
TLS_AES_256_GCM_SHA38429+29+
TLS_CHACHA20_POLY1305_SHA25629+29+
TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA1-81-8
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA1-81-8
TLS_DHE_DSS_WITH_AES_128_CBC_SHA9-229-22
TLS_DHE_DSS_WITH_AES_128_CBC_SHA25620-22
TLS_DHE_DSS_WITH_AES_128_GCM_SHA25620-22
TLS_DHE_DSS_WITH_AES_256_CBC_SHA9-2220-22
TLS_DHE_DSS_WITH_AES_256_CBC_SHA25620-22
TLS_DHE_DSS_WITH_AES_256_GCM_SHA38420-22
TLS_DHE_DSS_WITH_DES_CBC_SHA1-81-8
TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA1-81-8
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA1-81-8
TLS_DHE_RSA_WITH_AES_128_CBC_SHA9-259-25
TLS_DHE_RSA_WITH_AES_128_CBC_SHA25620-25
TLS_DHE_RSA_WITH_AES_128_GCM_SHA25620-2520-25
TLS_DHE_RSA_WITH_AES_256_CBC_SHA9-2520-25
TLS_DHE_RSA_WITH_AES_256_CBC_SHA25620-25
TLS_DHE_RSA_WITH_AES_256_GCM_SHA38420-2520-25
TLS_DHE_RSA_WITH_DES_CBC_SHA1-81-8
TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA1-8
TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA1-8
TLS_DH_DSS_WITH_DES_CBC_SHA1-8
TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA1-8
TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA1-8
TLS_DH_RSA_WITH_DES_CBC_SHA1-8
TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA1-8
TLS_DH_anon_WITH_3DES_EDE_CBC_SHA1-8
TLS_DH_anon_WITH_AES_128_CBC_SHA9-22
TLS_DH_anon_WITH_AES_128_CBC_SHA25620-22
TLS_DH_anon_WITH_AES_128_GCM_SHA25620-22
TLS_DH_anon_WITH_AES_256_CBC_SHA9-22
TLS_DH_anon_WITH_AES_256_CBC_SHA25620-22
TLS_DH_anon_WITH_AES_256_GCM_SHA38420-22
TLS_DH_anon_WITH_DES_CBC_SHA1-8
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA20-22
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA20+20+
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA25620-28
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA25620+20+
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA20+20+
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA38420-28
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA38420+20+
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA25624+24+
TLS_ECDHE_ECDSA_WITH_NULL_SHA20-22
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA20-2520-23
TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA21+21+
TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA21+21+
TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA25624+24+
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA20-22
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA20+20+
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA25620-28
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA25620+20+
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA20+20+
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA38420-28
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA38420+20+
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA25624+24+
TLS_ECDHE_RSA_WITH_NULL_SHA20-22
TLS_ECDHE_RSA_WITH_RC4_128_SHA20-2520-23
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA20-22
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA20-22
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA25620-22
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA25620-22
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA20-22
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA38420-22
TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA38420-22
TLS_ECDH_ECDSA_WITH_NULL_SHA20-22
TLS_ECDH_ECDSA_WITH_RC4_128_SHA20-22
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA20-22
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA20-22
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA25620-22
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA25620-22
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA20-22
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA38420-22
TLS_ECDH_RSA_WITH_AES_256_GCM_SHA38420-22
TLS_ECDH_RSA_WITH_NULL_SHA20-22
TLS_ECDH_RSA_WITH_RC4_128_SHA20-22
TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA20-22
TLS_ECDH_anon_WITH_AES_128_CBC_SHA20-22
TLS_ECDH_anon_WITH_AES_256_CBC_SHA20-22
TLS_ECDH_anon_WITH_NULL_SHA20-22
TLS_ECDH_anon_WITH_RC4_128_SHA20-22
TLS_EMPTY_RENEGOTIATION_INFO_SCSV20+20+
TLS_FALLBACK_SCSV21+
TLS_NULL_WITH_NULL_NULL1-8
TLS_PSK_WITH_3DES_EDE_CBC_SHA21-22
TLS_PSK_WITH_AES_128_CBC_SHA21+21+
TLS_PSK_WITH_AES_256_CBC_SHA21+21+
TLS_PSK_WITH_RC4_128_SHA21-25
TLS_RSA_EXPORT_WITH_DES40_CBC_SHA1-81-8
TLS_RSA_WITH_3DES_EDE_CBC_SHA1-81-8
TLS_RSA_WITH_AES_128_CBC_SHA9+9+
TLS_RSA_WITH_AES_128_CBC_SHA25620-28
TLS_RSA_WITH_AES_128_GCM_SHA25620+20+
TLS_RSA_WITH_AES_256_CBC_SHA9+20+
TLS_RSA_WITH_AES_256_CBC_SHA25620-28
TLS_RSA_WITH_AES_256_GCM_SHA38420+20+
TLS_RSA_WITH_DES_CBC_SHA1-81-8
TLS_RSA_WITH_NULL_MD51-8
TLS_RSA_WITH_NULL_SHA1-8
TLS_RSA_WITH_NULL_SHA25620-22
* *

NOTE: PSK cipher suites are enabled by default only if the {@code SSLContext} through * which the engine was created has been initialized with a {@code PSKKeyManager}. * * @see SSLContext * @see SSLSocket * @see SSLServerSocket * @see SSLSession * @see java.net.Socket * * @since 1.5 * @author Brad R. Wetmore */ public abstract class SSLEngine { private String peerHost = null; private int peerPort = -1; /** * Constructor for an SSLEngine providing no hints * for an internal session reuse strategy. * * @see SSLContext#createSSLEngine() * @see SSLSessionContext */ protected SSLEngine() { } /** * Constructor for an SSLEngine. *

* SSLEngine implementations may use the * peerHost and peerPort parameters as hints * for their internal session reuse strategy. *

* Some cipher suites (such as Kerberos) require remote hostname * information. Implementations of this class should use this * constructor to use Kerberos. *

* The parameters are not authenticated by the * SSLEngine. * * @param peerHost the name of the peer host * @param peerPort the port number of the peer * @see SSLContext#createSSLEngine(String, int) * @see SSLSessionContext */ protected SSLEngine(String peerHost, int peerPort) { this.peerHost = peerHost; this.peerPort = peerPort; } /** * Returns the host name of the peer. *

* Note that the value is not authenticated, and should not be * relied upon. * * @return the host name of the peer, or null if nothing is * available. */ public String getPeerHost() { return peerHost; } /** * Returns the port number of the peer. *

* Note that the value is not authenticated, and should not be * relied upon. * * @return the port number of the peer, or -1 if nothing is * available. */ public int getPeerPort() { return peerPort; } /** * Attempts to encode a buffer of plaintext application data into * SSL/TLS network data. *

* An invocation of this method behaves in exactly the same manner * as the invocation: *

     * {@link #wrap(ByteBuffer [], int, int, ByteBuffer)
     *     engine.wrap(new ByteBuffer [] { src }, 0, 1, dst);}
     * 
* * @param src * a ByteBuffer containing outbound application data * @param dst * a ByteBuffer to hold outbound network data * @return an SSLEngineResult describing the result * of this operation. * @throws SSLException * A problem was encountered while processing the * data that caused the SSLEngine to abort. * See the class description for more information on * engine closure. * @throws ReadOnlyBufferException * if the dst buffer is read-only. * @throws IllegalArgumentException * if either src or dst * is null. * @throws IllegalStateException if the client/server mode * has not yet been set. * @see #wrap(ByteBuffer [], int, int, ByteBuffer) */ public SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException { return wrap(new ByteBuffer [] { src }, 0, 1, dst); } /** * Attempts to encode plaintext bytes from a sequence of data * buffers into SSL/TLS network data. *

* An invocation of this method behaves in exactly the same manner * as the invocation: *

     * {@link #wrap(ByteBuffer [], int, int, ByteBuffer)
     *     engine.wrap(srcs, 0, srcs.length, dst);}
     * 
* * @param srcs * an array of ByteBuffers containing the * outbound application data * @param dst * a ByteBuffer to hold outbound network data * @return an SSLEngineResult describing the result * of this operation. * @throws SSLException * A problem was encountered while processing the * data that caused the SSLEngine to abort. * See the class description for more information on * engine closure. * @throws ReadOnlyBufferException * if the dst buffer is read-only. * @throws IllegalArgumentException * if either srcs or dst * is null, or if any element in srcs is null. * @throws IllegalStateException if the client/server mode * has not yet been set. * @see #wrap(ByteBuffer [], int, int, ByteBuffer) */ public SSLEngineResult wrap(ByteBuffer [] srcs, ByteBuffer dst) throws SSLException { if (srcs == null) { throw new IllegalArgumentException(""src == null""); } return wrap(srcs, 0, srcs.length, dst); } /** * Attempts to encode plaintext bytes from a subsequence of data * buffers into SSL/TLS network data. This ""gathering"" * operation encodes, in a single invocation, a sequence of bytes * from one or more of a given sequence of buffers. Gathering * wraps are often useful when implementing network [MASK] or * file formats that, for example, group data into segments * consisting of one or more fixed-length headers followed by a * variable-length body. See * {@link java.nio.channels.GatheringByteChannel} for more * information on gathering, and {@link * java.nio.channels.GatheringByteChannel#write(ByteBuffer[], * int, int)} for more information on the subsequence * behavior. *

* Depending on the state of the SSLEngine, this method may produce * network data without consuming any application data (for example, * it may generate handshake data.) *

* The application is responsible for reliably transporting the * network data to the peer, and for ensuring that data created by * multiple calls to wrap() is transported in the same order in which * it was generated. The application must properly synchronize * multiple calls to this method. *

* If this SSLEngine has not yet started its initial * handshake, this method will automatically start the handshake. *

* This method will attempt to produce SSL/TLS records, and will * consume as much source data as possible, but will never consume * more than the sum of the bytes remaining in each buffer. Each * ByteBuffer's position is updated to reflect the * amount of data consumed or produced. The limits remain the * same. *

* The underlying memory used by the srcs and * dst ByteBuffers must not be the same. *

* See the class description for more information on engine closure. * * @param srcs * an array of ByteBuffers containing the * outbound application data * @param offset * The offset within the buffer array of the first buffer from * which bytes are to be retrieved; it must be non-negative * and no larger than srcs.length * @param length * The maximum number of buffers to be accessed; it must be * non-negative and no larger than * srcs.length - offset * @param dst * a ByteBuffer to hold outbound network data * @return an SSLEngineResult describing the result * of this operation. * @throws SSLException * A problem was encountered while processing the * data that caused the SSLEngine to abort. * See the class description for more information on * engine closure. * @throws IndexOutOfBoundsException * if the preconditions on the offset and * length parameters do not hold. * @throws ReadOnlyBufferException * if the dst buffer is read-only. * @throws IllegalArgumentException * if either srcs or dst * is null, or if any element in the srcs * subsequence specified is null. * @throws IllegalStateException if the client/server mode * has not yet been set. * @see java.nio.channels.GatheringByteChannel * @see java.nio.channels.GatheringByteChannel#write( * ByteBuffer[], int, int) */ public abstract SSLEngineResult wrap(ByteBuffer [] srcs, int offset, int length, ByteBuffer dst) throws SSLException; /** * Attempts to decode SSL/TLS network data into a plaintext * application data buffer. *

* An invocation of this method behaves in exactly the same manner * as the invocation: *

     * {@link #unwrap(ByteBuffer, ByteBuffer [], int, int)
     *     engine.unwrap(src, new ByteBuffer [] { dst }, 0, 1);}
     * 
* * @param src * a ByteBuffer containing inbound network data. * @param dst * a ByteBuffer to hold inbound application data. * @return an SSLEngineResult describing the result * of this operation. * @throws SSLException * A problem was encountered while processing the * data that caused the SSLEngine to abort. * See the class description for more information on * engine closure. * @throws ReadOnlyBufferException * if the dst buffer is read-only. * @throws IllegalArgumentException * if either src or dst * is null. * @throws IllegalStateException if the client/server mode * has not yet been set. * @see #unwrap(ByteBuffer, ByteBuffer [], int, int) */ public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException { return unwrap(src, new ByteBuffer [] { dst }, 0, 1); } /** * Attempts to decode SSL/TLS network data into a sequence of plaintext * application data buffers. *

* An invocation of this method behaves in exactly the same manner * as the invocation: *

     * {@link #unwrap(ByteBuffer, ByteBuffer [], int, int)
     *     engine.unwrap(src, dsts, 0, dsts.length);}
     * 
* * @param src * a ByteBuffer containing inbound network data. * @param dsts * an array of ByteBuffers to hold inbound * application data. * @return an SSLEngineResult describing the result * of this operation. * @throws SSLException * A problem was encountered while processing the * data that caused the SSLEngine to abort. * See the class description for more information on * engine closure. * @throws ReadOnlyBufferException * if any of the dst buffers are read-only. * @throws IllegalArgumentException * if either src or dsts * is null, or if any element in dsts is null. * @throws IllegalStateException if the client/server mode * has not yet been set. * @see #unwrap(ByteBuffer, ByteBuffer [], int, int) */ public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer [] dsts) throws SSLException { if (dsts == null) { throw new IllegalArgumentException(""dsts == null""); } return unwrap(src, dsts, 0, dsts.length); } /** * Attempts to decode SSL/TLS network data into a subsequence of * plaintext application data buffers. This ""scattering"" * operation decodes, in a single invocation, a sequence of bytes * into one or more of a given sequence of buffers. Scattering * unwraps are often useful when implementing network [MASK] or * file formats that, for example, group data into segments * consisting of one or more fixed-length headers followed by a * variable-length body. See * {@link java.nio.channels.ScatteringByteChannel} for more * information on scattering, and {@link * java.nio.channels.ScatteringByteChannel#read(ByteBuffer[], * int, int)} for more information on the subsequence * behavior. *

* Depending on the state of the SSLEngine, this method may consume * network data without producing any application data (for example, * it may consume handshake data.) *

* The application is responsible for reliably obtaining the network * data from the peer, and for invoking unwrap() on the data in the * order it was received. The application must properly synchronize * multiple calls to this method. *

* If this SSLEngine has not yet started its initial * handshake, this method will automatically start the handshake. *

* This method will attempt to consume one complete SSL/TLS network * packet, but will never consume more than the sum of the bytes * remaining in the buffers. Each ByteBuffer's * position is updated to reflect the amount of data consumed or * produced. The limits remain the same. *

* The underlying memory used by the src and * dsts ByteBuffers must not be the same. *

* The inbound network buffer may be modified as a result of this * call: therefore if the network data packet is required for some * secondary purpose, the data should be duplicated before calling this * method. Note: the network data will not be useful to a second * SSLEngine, as each SSLEngine contains unique random state which * influences the SSL/TLS messages. *

* See the class description for more information on engine closure. * * @param src * a ByteBuffer containing inbound network data. * @param dsts * an array of ByteBuffers to hold inbound * application data. * @param offset * The offset within the buffer array of the first buffer from * which bytes are to be transferred; it must be non-negative * and no larger than dsts.length. * @param length * The maximum number of buffers to be accessed; it must be * non-negative and no larger than * dsts.length - offset. * @return an SSLEngineResult describing the result * of this operation. * @throws SSLException * A problem was encountered while processing the * data that caused the SSLEngine to abort. * See the class description for more information on * engine closure. * @throws IndexOutOfBoundsException * If the preconditions on the offset and * length parameters do not hold. * @throws ReadOnlyBufferException * if any of the dst buffers are read-only. * @throws IllegalArgumentException * if either src or dsts * is null, or if any element in the dsts * subsequence specified is null. * @throws IllegalStateException if the client/server mode * has not yet been set. * @see java.nio.channels.ScatteringByteChannel * @see java.nio.channels.ScatteringByteChannel#read( * ByteBuffer[], int, int) */ public abstract SSLEngineResult unwrap(ByteBuffer src, ByteBuffer [] dsts, int offset, int length) throws SSLException; /** * Returns a delegated Runnable task for * this SSLEngine. *

* SSLEngine operations may require the results of * operations that block, or may take an extended period of time to * complete. This method is used to obtain an outstanding {@link * java.lang.Runnable} operation (task). Each task must be assigned * a thread (possibly the current) to perform the {@link * java.lang.Runnable#run() run} operation. Once the * run method returns, the Runnable object * is no longer needed and may be discarded. *

* Delegated tasks run in the AccessControlContext * in place when this object was created. *

* A call to this method will return each outstanding task * exactly once. *

* Multiple delegated tasks can be run in parallel. * * @return a delegated Runnable task, or null * if none are available. */ public abstract Runnable getDelegatedTask(); /** * Signals that no more inbound network data will be sent * to this SSLEngine. *

* If the application initiated the closing process by calling * {@link #closeOutbound()}, under some circumstances it is not * required that the initiator wait for the peer's corresponding * close message. (See section 7.2.1 of the TLS specification (RFC 2246) for more * information on waiting for closure alerts.) In such cases, this * method need not be called. *

* But if the application did not initiate the closure process, or * if the circumstances above do not apply, this method should be * called whenever the end of the SSL/TLS data stream is reached. * This ensures closure of the inbound side, and checks that the * peer followed the SSL/TLS close procedure properly, thus * detecting possible truncation attacks. *

* This method is idempotent: if the inbound side has already * been closed, this method does not do anything. *

* {@link #wrap(ByteBuffer, ByteBuffer) wrap()} should be * called to flush any remaining handshake data. * * @throws SSLException * if this engine has not received the proper SSL/TLS close * notification message from the peer. * * @see #isInboundDone() * @see #isOutboundDone() */ public abstract void closeInbound() throws SSLException; /** * Returns whether {@link #unwrap(ByteBuffer, ByteBuffer)} will * accept any more inbound data messages. * * @return true if the SSLEngine will not * consume anymore network data (and by implication, * will not produce any more application data.) * @see #closeInbound() */ public abstract boolean isInboundDone(); /** * Signals that no more outbound application data will be sent * on this SSLEngine. *

* This method is idempotent: if the outbound side has already * been closed, this method does not do anything. *

* {@link #wrap(ByteBuffer, ByteBuffer)} should be * called to flush any remaining handshake data. * * @see #isOutboundDone() */ public abstract void closeOutbound(); /** * Returns whether {@link #wrap(ByteBuffer, ByteBuffer)} will * produce any more outbound data messages. *

* Note that during the closure phase, a SSLEngine may * generate handshake closure data that must be sent to the peer. * wrap() must be called to generate this data. When * this method returns true, no more outbound data will be created. * * @return true if the SSLEngine will not produce * any more network data * * @see #closeOutbound() * @see #closeInbound() */ public abstract boolean isOutboundDone(); // Android-changed: Added warnings about misuse /** * Returns the names of the cipher suites which could be enabled for use * on this engine. Normally, only a subset of these will actually * be enabled by default, since this list may include cipher suites which * do not meet quality of service requirements for those defaults. Such * cipher suites might be useful in specialized applications. * *

Applications should not blindly enable all supported * cipher suites. The supported cipher suites can include signaling cipher suite * values that can cause connection problems if enabled inappropriately. * *

The proper way to use this method is to either check if a specific cipher * suite is supported via {@code Arrays.asList(getSupportedCipherSuites()).contains(...)} * or to filter a desired list of cipher suites to only the supported ones via * {@code desiredSuiteSet.retainAll(Arrays.asList(getSupportedCipherSuites()))}. * * @return an array of cipher suite names * @see #getEnabledCipherSuites() * @see #setEnabledCipherSuites(String []) */ public abstract String [] getSupportedCipherSuites(); /** * Returns the names of the SSL cipher suites which are currently * enabled for use on this engine. When an SSLEngine is first * created, all enabled cipher suites support a minimum quality of * service. Thus, in some environments this value might be empty. *

* Even if a suite has been enabled, it might never be used. (For * example, the peer does not support it, the requisite * certificates/private keys for the suite are not available, or an * anonymous suite is enabled but authentication is required.) * * @return an array of cipher suite names * @see #getSupportedCipherSuites() * @see #setEnabledCipherSuites(String []) */ public abstract String [] getEnabledCipherSuites(); /** * Sets the cipher suites enabled for use on this engine. *

* Each cipher suite in the suites parameter must have * been listed by getSupportedCipherSuites(), or the method will * fail. Following a successful call to this method, only suites * listed in the suites parameter are enabled for use. *

* See {@link #getEnabledCipherSuites()} for more information * on why a specific cipher suite may never be used on a engine. * * @param suites Names of all the cipher suites to enable * @throws IllegalArgumentException when one or more of the ciphers * named by the parameter is not supported, or when the * parameter is null. * @see #getSupportedCipherSuites() * @see #getEnabledCipherSuites() */ public abstract void setEnabledCipherSuites(String suites []); /** * Returns the names of the [MASK] which could be enabled for use * with this SSLEngine. * * @return an array of [MASK] supported */ public abstract String [] getSupportedProtocols(); /** * Returns the names of the protocol versions which are currently * enabled for use with this SSLEngine. * * @return an array of [MASK] * @see #setEnabledProtocols(String []) */ public abstract String [] getEnabledProtocols(); // Android-added: Added paragraph about contiguous [MASK] . /** * Set the protocol versions enabled for use on this engine. *

* The [MASK] must have been listed by getSupportedProtocols() * as being supported. Following a successful call to this method, * only [MASK] listed in the [MASK] parameter * are enabled for use. *

* Because of the way the protocol version is negotiated, connections * will only be able to use a member of the lowest set of contiguous * enabled protocol versions. For example, enabling TLSv1.2 and TLSv1 * will result in connections only being able to use TLSv1. * * @param [MASK] Names of all the [MASK] to enable. * @throws IllegalArgumentException when one or more of * the [MASK] named by the parameter is not supported or * when the [MASK] parameter is null. * @see #getEnabledProtocols() */ public abstract void setEnabledProtocols(String [MASK] []); /** * Returns the SSLSession in use in this * SSLEngine. *

* These can be long lived, and frequently correspond to an entire * login session for some user. The session specifies a particular * cipher suite which is being actively used by all connections in * that session, as well as the identities of the session's client * and server. *

* Unlike {@link SSLSocket#getSession()} * this method does not block until handshaking is complete. *

* Until the initial handshake has completed, this method returns * a session object which reports an invalid cipher suite of * ""SSL_NULL_WITH_NULL_NULL"". * * @return the SSLSession for this SSLEngine * @see SSLSession */ public abstract SSLSession getSession(); /** * Returns the {@code SSLSession} being constructed during a SSL/TLS * handshake. *

* TLS [MASK] may negotiate parameters that are needed when using * an instance of this class, but before the {@code SSLSession} has * been completely initialized and made available via {@code getSession}. * For example, the list of valid signature algorithms may restrict * the type of certificates that can used during TrustManager * decisions, or the maximum TLS fragment packet sizes can be * resized to better support the network environment. *

* This method provides early access to the {@code SSLSession} being * constructed. Depending on how far the handshake has progressed, * some data may not yet be available for use. For example, if a * remote server will be sending a Certificate chain, but that chain * has yet not been processed, the {@code getPeerCertificates} * method of {@code SSLSession} will throw a * SSLPeerUnverifiedException. Once that chain has been processed, * {@code getPeerCertificates} will return the proper value. * * @see SSLSocket * @see SSLSession * @see ExtendedSSLSession * @see X509ExtendedKeyManager * @see X509ExtendedTrustManager * * @return null if this instance is not currently handshaking, or * if the current handshake has not progressed far enough to * create a basic SSLSession. Otherwise, this method returns the * {@code SSLSession} currently being negotiated. * @throws UnsupportedOperationException if the underlying provider * does not implement the operation. * * @since 1.7 */ public SSLSession getHandshakeSession() { throw new UnsupportedOperationException(); } /** * Initiates handshaking (initial or renegotiation) on this SSLEngine. *

* This method is not needed for the initial handshake, as the * wrap() and unwrap() methods will * implicitly call this method if handshaking has not already begun. *

* Note that the peer may also request a session renegotiation with * this SSLEngine by sending the appropriate * session renegotiate handshake message. *

* Unlike the {@link SSLSocket#startHandshake() * SSLSocket#startHandshake()} method, this method does not block * until handshaking is completed. *

* To force a complete SSL/TLS session renegotiation, the current * session should be invalidated prior to calling this method. *

* Some [MASK] may not support multiple handshakes on an existing * engine and may throw an SSLException. * * @throws SSLException * if a problem was encountered while signaling the * SSLEngine to begin a new handshake. * See the class description for more information on * engine closure. * @throws IllegalStateException if the client/server mode * has not yet been set. * @see SSLSession#invalidate() */ public abstract void beginHandshake() throws SSLException; /** * Returns the current handshake status for this SSLEngine. * * @return the current SSLEngineResult.HandshakeStatus. */ public abstract SSLEngineResult.HandshakeStatus getHandshakeStatus(); /** * Configures the engine to use client (or server) mode when * handshaking. *

* This method must be called before any handshaking occurs. * Once handshaking has begun, the mode can not be reset for the * life of this engine. *

* Servers normally authenticate themselves, and clients * are not required to do so. * * @param mode true if the engine should start its handshaking * in ""client"" mode * @throws IllegalArgumentException if a mode change is attempted * after the initial handshake has begun. * @see #getUseClientMode() */ public abstract void setUseClientMode(boolean mode); /** * Returns true if the engine is set to use client mode when * handshaking. * * @return true if the engine should do handshaking * in ""client"" mode * @see #setUseClientMode(boolean) */ public abstract boolean getUseClientMode(); /** * Configures the engine to require client authentication. This * option is only useful for engines in the server mode. *

* An engine's client authentication setting is one of the following: *

    *
  • client authentication required *
  • client authentication requested *
  • no client authentication desired *
*

* Unlike {@link #setWantClientAuth(boolean)}, if this option is set and * the client chooses not to provide authentication information * about itself, the negotiations will stop and the engine will * begin its closure procedure. *

* Calling this method overrides any previous setting made by * this method or {@link #setWantClientAuth(boolean)}. * * @param need set to true if client authentication is required, * or false if no client authentication is desired. * @see #getNeedClientAuth() * @see #setWantClientAuth(boolean) * @see #getWantClientAuth() * @see #setUseClientMode(boolean) */ public abstract void setNeedClientAuth(boolean need); /** * Returns true if the engine will require client authentication. * This option is only useful to engines in the server mode. * * @return true if client authentication is required, * or false if no client authentication is desired. * @see #setNeedClientAuth(boolean) * @see #setWantClientAuth(boolean) * @see #getWantClientAuth() * @see #setUseClientMode(boolean) */ public abstract boolean getNeedClientAuth(); /** * Configures the engine to request client authentication. * This option is only useful for engines in the server mode. *

* An engine's client authentication setting is one of the following: *

    *
  • client authentication required *
  • client authentication requested *
  • no client authentication desired *
*

* Unlike {@link #setNeedClientAuth(boolean)}, if this option is set and * the client chooses not to provide authentication information * about itself, the negotiations will continue. *

* Calling this method overrides any previous setting made by * this method or {@link #setNeedClientAuth(boolean)}. * * @param want set to true if client authentication is requested, * or false if no client authentication is desired. * @see #getWantClientAuth() * @see #setNeedClientAuth(boolean) * @see #getNeedClientAuth() * @see #setUseClientMode(boolean) */ public abstract void setWantClientAuth(boolean want); /** * Returns true if the engine will request client authentication. * This option is only useful for engines in the server mode. * * @return true if client authentication is requested, * or false if no client authentication is desired. * @see #setNeedClientAuth(boolean) * @see #getNeedClientAuth() * @see #setWantClientAuth(boolean) * @see #setUseClientMode(boolean) */ public abstract boolean getWantClientAuth(); /** * Controls whether new SSL sessions may be established by this engine. * If session creations are not allowed, and there are no * existing sessions to resume, there will be no successful * handshaking. * * @param flag true indicates that sessions may be created; this * is the default. false indicates that an existing session * must be resumed * @see #getEnableSessionCreation() */ public abstract void setEnableSessionCreation(boolean flag); /** * Returns true if new SSL sessions may be established by this engine. * * @return true indicates that sessions may be created; this * is the default. false indicates that an existing session * must be resumed * @see #setEnableSessionCreation(boolean) */ public abstract boolean getEnableSessionCreation(); /** * Returns the SSLParameters in effect for this SSLEngine. * The ciphersuites and [MASK] of the returned SSLParameters * are always non-null. * * @return the SSLParameters in effect for this SSLEngine. * @since 1.6 */ public SSLParameters getSSLParameters() { SSLParameters params = new SSLParameters(); params.setCipherSuites(getEnabledCipherSuites()); params.setProtocols(getEnabledProtocols()); if (getNeedClientAuth()) { params.setNeedClientAuth(true); } else if (getWantClientAuth()) { params.setWantClientAuth(true); } return params; } /** * Applies SSLParameters to this engine. * *

This means: *

    *
  • If {@code params.getCipherSuites()} is non-null, * {@code setEnabledCipherSuites()} is called with that value.
  • *
  • If {@code params.getProtocols()} is non-null, * {@code setEnabledProtocols()} is called with that value.
  • *
  • If {@code params.getNeedClientAuth()} or * {@code params.getWantClientAuth()} return {@code true}, * {@code setNeedClientAuth(true)} and * {@code setWantClientAuth(true)} are called, respectively; * otherwise {@code setWantClientAuth(false)} is called.
  • *
  • If {@code params.getServerNames()} is non-null, the engine will * configure its server names with that value.
  • *
  • If {@code params.getSNIMatchers()} is non-null, the engine will * configure its SNI matchers with that value.
  • *
* * @param params the parameters * @throws IllegalArgumentException if the setEnabledCipherSuites() or * the setEnabledProtocols() call fails * @since 1.6 */ public void setSSLParameters(SSLParameters params) { String[] s; s = params.getCipherSuites(); if (s != null) { setEnabledCipherSuites(s); } s = params.getProtocols(); if (s != null) { setEnabledProtocols(s); } if (params.getNeedClientAuth()) { setNeedClientAuth(true); } else if (params.getWantClientAuth()) { setWantClientAuth(true); } else { setWantClientAuth(false); } } // BEGIN Android-added: Integrate ALPN-related methods from OpenJDK 9+181 // Also removed references to DTLS in documentation; Android doesn't support DTLS. /** * Returns the most recent application protocol value negotiated for this * connection. *

* If supported by the underlying SSL/TLS implementation, * application name negotiation mechanisms such as RFC 7301 , the * Application-Layer Protocol Negotiation (ALPN), can negotiate * application-level values between peers. *

* @implSpec * The implementation in this class throws * {@code UnsupportedOperationException} and performs no other action. * * @return null if it has not yet been determined if application * [MASK] might be used for this connection, an empty * {@code String} if application [MASK] values will not * be used, or a non-empty application protocol {@code String} * if a value was successfully negotiated. * @throws UnsupportedOperationException if the underlying provider * does not implement the operation. * @since 9 */ public String getApplicationProtocol() { throw new UnsupportedOperationException(); } /** * Returns the application protocol value negotiated on a SSL/TLS * handshake currently in progress. *

* Like {@link #getHandshakeSession()}, * a connection may be in the middle of a handshake. The * application protocol may or may not yet be available. *

* @implSpec * The implementation in this class throws * {@code UnsupportedOperationException} and performs no other action. * * @return null if it has not yet been determined if application * [MASK] might be used for this handshake, an empty * {@code String} if application [MASK] values will not * be used, or a non-empty application protocol {@code String} * if a value was successfully negotiated. * @throws UnsupportedOperationException if the underlying provider * does not implement the operation. * @since 9 */ public String getHandshakeApplicationProtocol() { throw new UnsupportedOperationException(); } /** * Registers a callback function that selects an application protocol * value for a SSL/TLS handshake. * The function overrides any values supplied using * {@link SSLParameters#setApplicationProtocols * SSLParameters.setApplicationProtocols} and it supports the following * type parameters: *

*
*
{@code SSLEngine} *
The function's first argument allows the current {@code SSLEngine} * to be inspected, including the handshake session and configuration * settings. *
{@code List} *
The function's second argument lists the application protocol names * advertised by the TLS peer. *
{@code String} *
The function's result is an application protocol name, or null to * indicate that none of the advertised names are acceptable. * If the return value is an empty {@code String} then application * protocol indications will not be used. * If the return value is null (no value chosen) or is a value that * was not advertised by the peer, the underlying protocol will * determine what action to take. (For example, ALPN will send a * ""no_application_protocol"" alert and terminate the connection.) *
*
* * For example, the following call registers a callback function that * examines the TLS handshake parameters and selects an application protocol * name: *
{@code
     *     serverEngine.setHandshakeApplicationProtocolSelector(
     *         (serverEngine, clientProtocols) -> {
     *             SSLSession session = serverEngine.getHandshakeSession();
     *             return chooseApplicationProtocol(
     *                 serverEngine,
     *                 clientProtocols,
     *                 session.getProtocol(),
     *                 session.getCipherSuite());
     *         });
     * }
* * @apiNote * This method should be called by TLS server applications before the TLS * handshake begins. Also, this {@code SSLEngine} should be configured with * parameters that are compatible with the application protocol selected by * the callback function. For example, enabling a poor choice of cipher * suites could result in no suitable application protocol. * See {@link SSLParameters}. * * @implSpec * The implementation in this class throws * {@code UnsupportedOperationException} and performs no other action. * * @param selector the callback function, or null to disable the callback * functionality. * @throws UnsupportedOperationException if the underlying provider * does not implement the operation. * @since 9 */ public void setHandshakeApplicationProtocolSelector( BiFunction, String> selector) { throw new UnsupportedOperationException(); } /** * Retrieves the callback function that selects an application protocol * value during a SSL/TLS handshake. * See {@link #setHandshakeApplicationProtocolSelector * setHandshakeApplicationProtocolSelector} * for the function's type parameters. * * @implSpec * The implementation in this class throws * {@code UnsupportedOperationException} and performs no other action. * * @return the callback function, or null if none has been set. * @throws UnsupportedOperationException if the underlying provider * does not implement the operation. * @since 9 */ public BiFunction, String> getHandshakeApplicationProtocolSelector() { throw new UnsupportedOperationException(); } // END Android-added: Integrate ALPN-related methods from OpenJDK 9+181 } ","protocols " "// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android; import com.android.SdkConstants; import com.android.resources.ResourceType; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.resources.ResourceTypeEnum; import com.google.devtools.build.android.xml.AttrXmlResourceValue; import com.google.devtools.build.android.xml.IdXmlResourceValue; import com.google.devtools.build.android.xml.MacroXmlResourceValue; import com.google.devtools.build.android.xml.Namespaces; import com.google.devtools.build.android.xml.PluralXmlResourceValue; import com.google.devtools.build.android.xml.PublicXmlResourceValue; import com.google.devtools.build.android.xml.SimpleXmlResourceValue; import com.google.devtools.build.android.xml.StyleXmlResourceValue; import com.google.devtools.build.android.xml.StyleableXmlResourceValue; import com.google.protobuf.CodedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.nio.file.Path; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.Characters; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.Namespace; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; /** * {@link XmlResourceValues} provides methods for getting {@link XmlResourceValue} derived classes. * *

Acts a static factory class containing the general xml parsing logic for resources that are * declared inside the <resources> tag. */ public class XmlResourceValues { private static final Logger logger = Logger.getLogger(XmlResourceValues.class.getCanonicalName()); private static final QName TAG_EAT_COMMENT = QName.valueOf(""eat-comment""); private static final QName TAG_PLURALS = QName.valueOf(""plurals""); private static final QName ATTR_QUANTITY = QName.valueOf(""quantity""); private static final QName TAG_ATTR = QName.valueOf(""attr""); private static final QName TAG_DECLARE_STYLEABLE = QName.valueOf(""declare-styleable""); private static final QName TAG_ITEM = QName.valueOf(""item""); private static final QName TAG_STYLE = QName.valueOf(""style""); private static final QName TAG_SKIP = QName.valueOf(""skip""); private static final QName TAG_RESOURCES = QName.valueOf(""resources""); private static final QName ATTR_FORMAT = QName.valueOf(""format""); private static final QName ATTR_NAME = QName.valueOf(""name""); private static final QName ATTR_VALUE = QName.valueOf(""value""); private static final QName ATTR_PARENT = QName.valueOf(""parent""); private static final QName ATTR_TYPE = QName.valueOf(""type""); private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); private static XMLInputFactory inputFactoryInstance = null; public static XMLInputFactory getXmlInputFactory() { if (inputFactoryInstance == null) { inputFactoryInstance = XMLInputFactory.newInstance(); inputFactoryInstance.setProperty( ""http://java.sun.com/xml/stream/properties/report-cdata-event"", Boolean.TRUE); } return inputFactoryInstance; } static XmlResourceValue parsePlurals( XMLEventReader eventReader, StartElement start, Namespaces.Collector namespacesCollector) throws XMLStreamException { ImmutableMap.Builder values = ImmutableMap.builder(); namespacesCollector.collectFrom(start); for (XMLEvent element = nextTag(eventReader); !isEndTag(element, TAG_PLURALS); element = nextTag(eventReader)) { if (isItem(element)) { if (!element.isStartElement()) { throw new XMLStreamException( String.format(""Expected start element %s"", element), element.getLocation()); } String contents = readContentsAsString( eventReader, element.asStartElement().getName(), namespacesCollector); values.put( getElementAttributeByName(element.asStartElement(), ATTR_QUANTITY), contents == null ? """" : contents); } } return PluralXmlResourceValue.createWithAttributesAndValues( ImmutableMap.copyOf(parseTagAttributes(start)), values.buildOrThrow()); } static XmlResourceValue parseStyle(XMLEventReader eventReader, StartElement start) throws XMLStreamException { Map values = new LinkedHashMap<>(); for (XMLEvent element = nextTag(eventReader); !isEndTag(element, TAG_STYLE); element = nextTag(eventReader)) { if (isItem(element)) { values.put(getElementName(element.asStartElement()), eventReader.getElementText()); } } // Parents can be declared as: // ?style/parent // @style/parent // // And, in the resource name . // Here, we take a garbage in, garbage out approach and just read the xml value raw. return StyleXmlResourceValue.of(getElementAttributeByName(start, ATTR_PARENT), values); } static void parseDeclareStyleable( FullyQualifiedName.Factory fqnFactory, Path path, KeyValueConsumer overwritingConsumer, KeyValueConsumer combiningConsumer, XMLEventReader eventReader, StartElement start) throws XMLStreamException { Map members = new LinkedHashMap<>(); for (XMLEvent element = nextTag(eventReader); !isEndTag(element, TAG_DECLARE_STYLEABLE); element = nextTag(eventReader)) { if (isStartTag(element, TAG_ATTR)) { StartElement attr = element.asStartElement(); FullyQualifiedName attrName = fqnFactory.create(ResourceType.ATTR, getElementName(attr)); // If there is format and the next tag is a starting tag, treat it as an attr definition. // Without those, it will be an attr reference. if (XmlResourceValues.getElementAttributeByName(attr, ATTR_FORMAT) != null || (XmlResourceValues.peekNextTag(eventReader) != null && XmlResourceValues.peekNextTag(eventReader).isStartElement())) { overwritingConsumer.accept( attrName, DataResourceXml.createWithNoNamespace(path, parseAttr(eventReader, attr))); members.put(attrName, Boolean.TRUE); } else { members.put(attrName, Boolean.FALSE); } } } combiningConsumer.accept( fqnFactory.create(ResourceType.STYLEABLE, getElementName(start)), DataResourceXml.createWithNoNamespace(path, StyleableXmlResourceValue.of(members))); } static XmlResourceValue parseAttr(XMLEventReader eventReader, StartElement start) throws XMLStreamException { XmlResourceValue value = AttrXmlResourceValue.from( start, getElementAttributeByName(start, ATTR_FORMAT), eventReader); return value; } static XmlResourceValue parseId( XMLEventReader eventReader, StartElement start, Namespaces.Collector namespacesCollector) throws XMLStreamException { try { if (XmlResourceValues.isEndTag(eventReader.peek(), start.getName())) { return IdXmlResourceValue.of(); } else { return IdXmlResourceValue.of( readContentsAsString( eventReader, start.getName(), namespacesCollector.collectFrom(start))); } } catch (IllegalArgumentException e) { throw new XMLStreamException(e); } } static XmlResourceValue parseSimple( XMLEventReader eventReader, ResourceType resourceType, StartElement start, Namespaces.Collector namespacesCollector) throws XMLStreamException { String contents; namespacesCollector.collectFrom(start); // Check that the element is unary. If it is, the contents is null if (isEndTag(eventReader.peek(), start.getName())) { contents = null; } else { contents = readContentsAsString(eventReader, start.getName(), namespacesCollector); } return SimpleXmlResourceValue.of( start.getName().equals(TAG_ITEM) ? SimpleXmlResourceValue.Type.ITEM : SimpleXmlResourceValue.Type.from(resourceType), ImmutableMap.copyOf(parseTagAttributes(start)), contents); } static XmlResourceValue parsePublic( XMLEventReader eventReader, StartElement start, Namespaces.Collector namespacesCollector) throws XMLStreamException { namespacesCollector.collectFrom(start); // The tag should be unary. if (!isEndTag(eventReader.peek(), start.getName())) { throw new XMLStreamException( String.format("" tag should be unary %s"", start), start.getLocation()); } // The tag should have a valid type attribute, and optionally an id attribute. ImmutableMap attributes = ImmutableMap.copyOf(parseTagAttributes(start)); String typeAttr = attributes.get(SdkConstants.ATTR_TYPE); ResourceType type; if (typeAttr != null) { type = ResourceTypeEnum.get(typeAttr); if (type == null || type == ResourceType.PUBLIC) { throw new XMLStreamException( String.format("" tag has invalid type attribute %s"", start), start.getLocation()); } } else { throw new XMLStreamException( String.format("" tag missing type attribute %s"", start), start.getLocation()); } String idValueAttr = attributes.get(SdkConstants.ATTR_ID); Optional id = Optional.absent(); if (idValueAttr != null) { try { id = Optional.of(Integer.decode(idValueAttr)); } catch (NumberFormatException e) { throw new XMLStreamException( String.format("" has invalid id number %s"", start), start.getLocation(), e); } } if (attributes.size() > 2) { throw new XMLStreamException( String.format("" has unexpected attributes %s"", start), start.getLocation()); } return PublicXmlResourceValue.create(type, id); } public static Map parseTagAttributes(StartElement start) { // Using a map to deduplicate xmlns declarations on the attributes. Map attributeMap = new LinkedHashMap<>(); Iterator attributes = iterateAttributesFrom(start); while (attributes.hasNext()) { Attribute attribute = attributes.next(); QName name = attribute.getName(); // Name used as the resource key, so skip it here. if (ATTR_NAME.equals(name)) { continue; } String value = escapeXmlValues(attribute.getValue()).replace(""\"""", """""); if (!name.getNamespaceURI().isEmpty()) { attributeMap.put(name.getPrefix() + "":"" + attribute.getName().getLocalPart(), value); } else { attributeMap.put(attribute.getName().getLocalPart(), value); } Iterator namespaces = iterateNamespacesFrom(start); while (namespaces.hasNext()) { Namespace namespace = namespaces.next(); attributeMap.put(""xmlns:"" + namespace.getPrefix(), namespace.getNamespaceURI()); } } return attributeMap; } static XmlResourceValue parseMacro( XMLEventReader eventReader, StartElement start, Namespaces.Collector namespacesCollector) throws XMLStreamException { if (isEndTag(eventReader.peek(), start.getName())) { throw new XMLStreamException( String.format("" must have contents %s"", start), start.getLocation()); } String contents = readContentsAsString(eventReader, start.getName(), namespacesCollector); return MacroXmlResourceValue.of(contents); } // TODO(corysmith): Replace this with real escaping system, preferably a performant high level xml // writing library. See AndroidDataWritingVisitor TODO. private static String escapeXmlValues(String data) { return data.replace(""&"", ""&"").replace(""<"", ""<"").replace("">"", "">""); } /** * Reads the xml events as a string until finding a closing tag. * * @param eventReader The current xml stream. * @param startTag The name of the tag to close on. * @param namespacesCollector A builder for collecting namespaces. * @return A xml escaped string representation of the xml stream */ @Nullable public static String readContentsAsString( XMLEventReader eventReader, QName startTag, Namespaces.Collector namespacesCollector) throws XMLStreamException { StringWriter contents = new StringWriter(); XMLEventWriter writer = XML_OUTPUT_FACTORY.createXMLEventWriter(contents); while (!isEndTag(eventReader.peek(), startTag)) { XMLEvent xmlEvent = (XMLEvent) eventReader.next(); if (xmlEvent.isStartElement()) { namespacesCollector.collectFrom(xmlEvent.asStartElement()); writer.add(xmlEvent); } else { writer.add(xmlEvent); } } // Verify the end element. EndElement endElement = eventReader.nextEvent().asEndElement(); Preconditions.checkArgument(endElement.getName().equals(startTag)); return contents.toString(); } public static Iterator iterateAttributesFrom(StartElement start) { return start.getAttributes(); } public static Iterator iterateNamespacesFrom(StartElement start) { return start.getNamespaces(); } /* XML helper methods follow. */ // TODO(corysmith): Move these to a wrapper class for XMLEventReader. @Nullable public static String getElementAttributeByName(StartElement element, QName name) { Attribute attribute = element.getAttributeByName(name); return attribute == null ? null : attribute.getValue(); } public static String getElementValue(StartElement start) { return getElementAttributeByName(start, ATTR_VALUE); } public static String getElementName(StartElement start) { return getElementAttributeByName(start, ATTR_NAME); } public static String getElementType(StartElement start) { return getElementAttributeByName(start, ATTR_TYPE); } public static boolean isTag(XMLEvent event, QName [MASK] ) { if (event.isStartElement()) { return isStartTag(event, [MASK] ); } if (event.isEndElement()) { return isEndTag(event, [MASK] ); } return false; } public static boolean isItem(XMLEvent start) { return isTag(start, TAG_ITEM); } public static boolean isEndTag(XMLEvent event, QName [MASK] ) { if (event.isEndElement()) { return [MASK] .equals(event.asEndElement().getName()); } return false; } public static boolean isStartTag(XMLEvent event, QName [MASK] ) { if (event.isStartElement()) { return [MASK] .equals(event.asStartElement().getName()); } return false; } public static XMLEvent nextTag(XMLEventReader eventReader) throws XMLStreamException { while (eventReader.hasNext() && !(eventReader.peek().isEndElement() || eventReader.peek().isStartElement())) { XMLEvent nextEvent = eventReader.nextEvent(); if (nextEvent.isCharacters() && !nextEvent.asCharacters().isIgnorableWhiteSpace()) { Characters characters = nextEvent.asCharacters(); // TODO(corysmith): Turn into a warning with the Path is available to add to it. // This case is when unexpected characters are thrown into the xml. Best case, it's a // incorrect comment type... logger.fine( String.format( ""Invalid characters [%s] found at %s"", characters.getData(), characters.getLocation().getLineNumber())); } } return eventReader.nextEvent(); } public static XMLEvent peekNextTag(XMLEventReader eventReader) throws XMLStreamException { while (eventReader.hasNext() && !(eventReader.peek().isEndElement() || eventReader.peek().isStartElement())) { eventReader.nextEvent(); } return eventReader.peek(); } @Nullable static StartElement findNextStart(XMLEventReader eventReader) throws XMLStreamException { while (eventReader.hasNext()) { XMLEvent event = eventReader.nextEvent(); if (event.isStartElement()) { return event.asStartElement(); } } return null; } static StartElement moveToResources(XMLEventReader eventReader) throws XMLStreamException { while (eventReader.hasNext()) { StartElement next = findNextStart(eventReader); if (next != null && next.getName().equals(TAG_RESOURCES)) { return next; } } return null; } public static SerializeFormat.DataValue.Builder newSerializableDataValueBuilder(int sourceId) { SerializeFormat.DataValue.Builder builder = SerializeFormat.DataValue.newBuilder(); return builder.setSourceId(sourceId); } public static int serializeProtoDataValue( OutputStream output, SerializeFormat.DataValue.Builder builder) throws IOException { SerializeFormat.DataValue value = builder.build(); value.writeDelimitedTo(output); return CodedOutputStream.computeUInt32SizeNoTag(value.getSerializedSize()) + value.getSerializedSize(); } public static boolean isEatComment(StartElement start) { return isTag(start, TAG_EAT_COMMENT); } public static boolean isSkip(StartElement start) { return isTag(start, TAG_SKIP); } } ","subTagType " "/* * Copy [MASK] (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.ui; import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; import static com.google.android.exoplayer2.util.Util.getDrawable; import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.opengl.GLSurfaceView; import android.os.Looper; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.content.ContextCompat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; import com.google.common.collect.ImmutableList; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * A high level view for {@link Player} media playbacks. It displays video, subtitles and album art * during playback, and displays playback controls using a {@link PlayerControlView}. * *

A PlayerView can be customized by setting attributes (or calling corresponding methods), * overriding drawables, overriding the view's layout file, or by specifying a custom view layout * file. * *

Attributes

* * The following attributes can be set on a PlayerView when used in a layout XML file: * *
    *
  • {@code use_artwork} - Whether artwork is used if available in audio streams. *
      *
    • Corresponding method: {@link #setUseArtwork(boolean)} *
    • Default: {@code true} *
    *
  • {@code default_artwork} - Default artwork to use if no artwork available in audio * streams. *
      *
    • Corresponding method: {@link #setDefaultArtwork(Drawable)} *
    • Default: {@code null} *
    *
  • {@code use_controller} - Whether the playback controls can be shown. *
      *
    • Corresponding method: {@link #setUseController(boolean)} *
    • Default: {@code true} *
    *
  • {@code hide_on_touch} - Whether the playback controls are hidden by touch events. *
      *
    • Corresponding method: {@link #setControllerHideOnTouch(boolean)} *
    • Default: {@code true} *
    *
  • {@code auto_show} - Whether the playback controls are automatically shown when * playback starts, pauses, ends, or fails. If set to false, the playback controls can be * manually operated with {@link #showController()} and {@link #hideController()}. *
      *
    • Corresponding method: {@link #setControllerAutoShow(boolean)} *
    • Default: {@code true} *
    *
  • {@code hide_during_ads} - Whether the playback controls are hidden during ads. * Controls are always shown during ads if they are enabled and the player is paused. *
      *
    • Corresponding method: {@link #setControllerHideDuringAds(boolean)} *
    • Default: {@code true} *
    *
  • {@code show_buffering} - Whether the buffering spinner is displayed when the player * is buffering. Valid values are {@code never}, {@code when_playing} and {@code always}. *
      *
    • Corresponding method: {@link #setShowBuffering(int)} *
    • Default: {@code never} *
    *
  • {@code resize_mode} - Controls how video and album art is resized within the view. * Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height}, {@code fill} and * {@code zoom}. *
      *
    • Corresponding method: {@link #setResizeMode(int)} *
    • Default: {@code fit} *
    *
  • {@code surface_type} - The type of surface view used for video playbacks. Valid * values are {@code surface_view}, {@code texture_view}, {@code spherical_gl_surface_view}, * {@code video_decoder_gl_surface_view} and {@code none}. Using {@code none} is recommended * for audio only applications, since creating the surface can be expensive. Using {@code * surface_view} is recommended for video applications. Note, TextureView can only be used in * a hardware accelerated window. When rendered in software, TextureView will draw nothing. *
      *
    • Corresponding method: None *
    • Default: {@code surface_view} *
    *
  • {@code shutter_background_color} - The background color of the {@code exo_shutter} * view. *
      *
    • Corresponding method: {@link #setShutterBackgroundColor(int)} *
    • Default: {@code unset} *
    *
  • {@code keep_content_on_player_reset} - Whether the currently displayed video frame * or media artwork is kept visible when the player is reset. *
      *
    • Corresponding method: {@link #setKeepContentOnPlayerReset(boolean)} *
    • Default: {@code false} *
    *
  • {@code player_layout_id} - Specifies the id of the layout to be inflated. See below * for more details. *
      *
    • Corresponding method: None *
    • Default: {@code R.layout.exo_player_view} *
    *
  • {@code controller_layout_id} - Specifies the id of the layout resource to be * inflated by the child {@link PlayerControlView}. See below for more details. *
      *
    • Corresponding method: None *
    • Default: {@code R.layout.exo_player_control_view} *
    *
  • All attributes that can be set on {@link PlayerControlView} and {@link DefaultTimeBar} can * also be set on a PlayerView, and will be propagated to the inflated {@link * PlayerControlView} unless the layout is overridden to specify a custom {@code * exo_controller} (see below). *
* *

Overriding drawables

* * The drawables used by {@link PlayerControlView} (with its default layout file) can be overridden * by drawables with the same names defined in your application. See the {@link PlayerControlView} * documentation for a list of drawables that can be overridden. * *

Overriding the layout file

* * To customize the layout of PlayerView throughout your app, or just for certain configurations, * you can define {@code exo_player_view.xml} layout files in your application {@code res/layout*} * directories. These layouts will override the one provided by the library, and will be inflated * for use by PlayerView. The view identifies and binds its children by looking for the following * ids: * *
    *
  • {@code exo_content_frame} - A frame whose aspect ratio is resized based on the video * or album art of the media being played, and the configured {@code resize_mode}. The video * surface view is inflated into this frame as its first child. *
      *
    • Type: {@link AspectRatioFrameLayout} *
    *
  • {@code exo_shutter} - A view that's made visible when video should be hidden. This * view is typically an opaque view that covers the video surface, thereby obscuring it when * visible. Obscuring the surface in this way also helps to prevent flicker at the start of * playback when {@code surface_type=""surface_view""}. *
      *
    • Type: {@link View} *
    *
  • {@code exo_buffering} - A view that's made visible when the player is buffering. * This view typically displays a buffering spinner or animation. *
      *
    • Type: {@link View} *
    *
  • {@code exo_subtitles} - Displays subtitles. *
      *
    • Type: {@link SubtitleView} *
    *
  • {@code exo_artwork} - Displays album art. *
      *
    • Type: {@link ImageView} *
    *
  • {@code exo_error_message} - Displays an error message to the user if playback fails. *
      *
    • Type: {@link TextView} *
    *
  • {@code exo_controller_placeholder} - A placeholder that's replaced with the inflated * {@link PlayerControlView}. Ignored if an {@code exo_controller} view exists. *
      *
    • Type: {@link View} *
    *
  • {@code exo_controller} - An already inflated {@link PlayerControlView}. Allows use * of a custom extension of {@link PlayerControlView}. {@link PlayerControlView} and {@link * DefaultTimeBar} attributes set on the PlayerView will not be automatically propagated * through to this instance. If a view exists with this id, any {@code * exo_controller_placeholder} view will be ignored. *
      *
    • Type: {@link PlayerControlView} *
    *
  • {@code exo_ad_overlay} - A {@link FrameLayout} positioned on top of the player which * is used to show ad UI (if applicable). *
      *
    • Type: {@link FrameLayout} *
    *
  • {@code exo_overlay} - A {@link FrameLayout} positioned on top of the player which * the app can access via {@link #getOverlayFrameLayout()}, provided for convenience. *
      *
    • Type: {@link FrameLayout} *
    *
* *

All child views are optional and so can be omitted if not required, however where defined they * must be of the expected type. * *

Specifying a custom layout file

* * Defining your own {@code exo_player_view.xml} is useful to customize the layout of PlayerView * throughout your application. It's also possible to customize the layout for a single instance in * a layout file. This is achieved by setting the {@code player_layout_id} attribute on a * PlayerView. This will cause the specified layout to be inflated instead of {@code * exo_player_view.xml} for only the instance on which the attribute is set. * * @deprecated Use {@link StyledPlayerView} instead. */ @Deprecated public class PlayerView extends FrameLayout implements AdViewProvider { /** * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}. */ @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS}) public @interface ShowBuffering {} /** The buffering view is never shown. */ public static final int SHOW_BUFFERING_NEVER = 0; /** * The buffering view is shown when the player is in the {@link Player#STATE_BUFFERING buffering} * state and {@link Player#getPlayWhenReady() playWhenReady} is {@code true}. */ public static final int SHOW_BUFFERING_WHEN_PLAYING = 1; /** * The buffering view is always shown when the player is in the {@link Player#STATE_BUFFERING * buffering} state. */ public static final int SHOW_BUFFERING_ALWAYS = 2; private static final int SURFACE_TYPE_NONE = 0; private static final int SURFACE_TYPE_SURFACE_VIEW = 1; private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; private static final int SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW = 3; private static final int SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW = 4; private final ComponentListener componentListener; @Nullable private final AspectRatioFrameLayout contentFrame; @Nullable private final View shutterView; @Nullable private final View surfaceView; private final boolean surfaceViewIgnoresVideoAspectRatio; @Nullable private final ImageView artworkView; @Nullable private final SubtitleView subtitleView; @Nullable private final View bufferingView; @Nullable private final TextView errorMessageView; @Nullable private final PlayerControlView controller; @Nullable private final FrameLayout adOverlayFrameLayout; @Nullable private final FrameLayout overlayFrameLayout; @Nullable private Player player; private boolean useController; @Nullable private PlayerControlView.VisibilityListener controllerVisibilityListener; private boolean useArtwork; @Nullable private Drawable defaultArtwork; private @ShowBuffering int showBuffering; private boolean keepContentOnPlayerReset; @Nullable private ErrorMessageProvider errorMessageProvider; @Nullable private CharSequence customErrorMessage; private int controllerShowTimeoutMs; private boolean controllerAutoShow; private boolean controllerHideDuringAds; private boolean controllerHideOnTouch; private int textureViewRotation; private boolean isTouching; public PlayerView(Context context) { this(context, /* attrs= */ null); } public PlayerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, /* defStyleAttr= */ 0); } @SuppressWarnings({""nullness:argument"", ""nullness:method.invocation""}) public PlayerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); componentListener = new ComponentListener(); if (isInEditMode()) { contentFrame = null; shutterView = null; surfaceView = null; surfaceViewIgnoresVideoAspectRatio = false; artworkView = null; subtitleView = null; bufferingView = null; errorMessageView = null; controller = null; adOverlayFrameLayout = null; overlayFrameLayout = null; ImageView logo = new ImageView(context); if (Util.SDK_INT >= 23) { configureEditModeLogoV23(context, getResources(), logo); } else { configureEditModeLogo(context, getResources(), logo); } addView(logo); return; } boolean shutterColorSet = false; int shutterColor = 0; int playerLayoutId = R.layout.exo_player_view; boolean useArtwork = true; int defaultArtworkId = 0; boolean useController = true; int surfaceType = SURFACE_TYPE_SURFACE_VIEW; int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; int controllerShowTimeoutMs = PlayerControlView.DEFAULT_SHOW_TIMEOUT_MS; boolean controllerHideOnTouch = true; boolean controllerAutoShow = true; boolean controllerHideDuringAds = true; int showBuffering = SHOW_BUFFERING_NEVER; if (attrs != null) { TypedArray a = context .getTheme() .obtainStyledAttributes( attrs, R.styleable.PlayerView, defStyleAttr, /* defStyleRes= */ 0); try { shutterColorSet = a.hasValue(R.styleable.PlayerView_shutter_background_color); shutterColor = a.getColor(R.styleable.PlayerView_shutter_background_color, shutterColor); playerLayoutId = a.getResourceId(R.styleable.PlayerView_player_layout_id, playerLayoutId); useArtwork = a.getBoolean(R.styleable.PlayerView_use_artwork, useArtwork); defaultArtworkId = a.getResourceId(R.styleable.PlayerView_default_artwork, defaultArtworkId); useController = a.getBoolean(R.styleable.PlayerView_use_controller, useController); surfaceType = a.getInt(R.styleable.PlayerView_surface_type, surfaceType); resizeMode = a.getInt(R.styleable.PlayerView_resize_mode, resizeMode); controllerShowTimeoutMs = a.getInt(R.styleable.PlayerView_show_timeout, controllerShowTimeoutMs); controllerHideOnTouch = a.getBoolean(R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch); controllerAutoShow = a.getBoolean(R.styleable.PlayerView_auto_show, controllerAutoShow); showBuffering = a.getInteger(R.styleable.PlayerView_show_buffering, showBuffering); keepContentOnPlayerReset = a.getBoolean( R.styleable.PlayerView_keep_content_on_player_reset, keepContentOnPlayerReset); controllerHideDuringAds = a.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds); } finally { a.recycle(); } } LayoutInflater.from(context).inflate(playerLayoutId, this); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); // Content frame. contentFrame = findViewById(R.id.exo_content_frame); if (contentFrame != null) { setResizeModeRaw(contentFrame, resizeMode); } // Shutter view. shutterView = findViewById(R.id.exo_shutter); if (shutterView != null && shutterColorSet) { shutterView.setBackgroundColor(shutterColor); } // Create a surface view and insert it into the content frame, if there is one. boolean surfaceViewIgnoresVideoAspectRatio = false; if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) { ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); switch (surfaceType) { case SURFACE_TYPE_TEXTURE_VIEW: surfaceView = new TextureView(context); break; case SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW: try { Class clazz = Class.forName( ""com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView""); surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context); } catch (Exception e) { throw new IllegalStateException( ""spherical_gl_surface_view requires an ExoPlayer dependency"", e); } surfaceViewIgnoresVideoAspectRatio = true; break; case SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW: try { Class clazz = Class.forName(""com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView""); surfaceView = (View) clazz.getConstructor(Context.class).newInstance(context); } catch (Exception e) { throw new IllegalStateException( ""video_decoder_gl_surface_view requires an ExoPlayer dependency"", e); } break; default: surfaceView = new SurfaceView(context); break; } surfaceView.setLayoutParams(params); // We don't want surfaceView to be clickable separately to the PlayerView itself, but we // do want to register as an OnClickListener so that surfaceView implementations can propagate // click events up to the PlayerView by calling their own performClick method. surfaceView.setOnClickListener(componentListener); surfaceView.setClickable(false); contentFrame.addView(surfaceView, 0); } else { surfaceView = null; } this.surfaceViewIgnoresVideoAspectRatio = surfaceViewIgnoresVideoAspectRatio; // Ad overlay frame layout. adOverlayFrameLayout = findViewById(R.id.exo_ad_overlay); // Overlay frame layout. overlayFrameLayout = findViewById(R.id.exo_overlay); // Artwork view. artworkView = findViewById(R.id.exo_artwork); this.useArtwork = useArtwork && artworkView != null; if (defaultArtworkId != 0) { defaultArtwork = ContextCompat.getDrawable(getContext(), defaultArtworkId); } // Subtitle view. subtitleView = findViewById(R.id.exo_subtitles); if (subtitleView != null) { subtitleView.setUserDefaultStyle(); subtitleView.setUserDefaultTextSize(); } // Buffering view. bufferingView = findViewById(R.id.exo_buffering); if (bufferingView != null) { bufferingView.setVisibility(View.GONE); } this.showBuffering = showBuffering; // Error message view. errorMessageView = findViewById(R.id.exo_error_message); if (errorMessageView != null) { errorMessageView.setVisibility(View.GONE); } // Playback control view. PlayerControlView customController = findViewById(R.id.exo_controller); View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder); if (customController != null) { this.controller = customController; } else if (controllerPlaceholder != null) { // Propagate attrs as playbackAttrs so that PlayerControlView's custom attributes are // transferred, but standard attributes (e.g. background) are not. this.controller = new PlayerControlView(context, null, 0, attrs); controller.setId(R.id.exo_controller); controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); int controllerIndex = parent.indexOfChild(controllerPlaceholder); parent.removeView(controllerPlaceholder); parent.addView(controller, controllerIndex); } else { this.controller = null; } this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0; this.controllerHideOnTouch = controllerHideOnTouch; this.controllerAutoShow = controllerAutoShow; this.controllerHideDuringAds = controllerHideDuringAds; this.useController = useController && controller != null; if (controller != null) { controller.hide(); controller.addVisibilityListener(/* listener= */ componentListener); } if (useController) { setClickable(true); } updateContentDescription(); } /** * Switches the view targeted by a given {@link Player}. * * @param player The player whose target view is being switched. * @param oldPlayerView The old view to detach from the player. * @param newPlayerView The new view to attach to the player. */ public static void switchTargetView( Player player, @Nullable PlayerView oldPlayerView, @Nullable PlayerView newPlayerView) { if (oldPlayerView == newPlayerView) { return; } // We attach the new view before detaching the old one because this ordering allows the player // to swap directly from one surface to another, without transitioning through a state where no // surface is attached. This is significantly more efficient and achieves a more seamless // transition when using platform provided video decoders. if (newPlayerView != null) { newPlayerView.setPlayer(player); } if (oldPlayerView != null) { oldPlayerView.setPlayer(null); } } /** Returns the player currently set on this view, or null if no player is set. */ @Nullable public Player getPlayer() { return player; } /** * Sets the {@link Player} to use. * *

To transition a {@link Player} from targeting one view to another, it's recommended to use * {@link #switchTargetView(Player, PlayerView, PlayerView)} rather than this method. If you do * wish to use this method directly, be sure to attach the player to the new view before * calling {@code setPlayer(null)} to detach it from the old one. This ordering is significantly * more efficient and may allow for more seamless transitions. * * @param player The {@link Player} to use, or {@code null} to detach the current player. Only * players which are accessed on the main thread are supported ({@code * player.getApplicationLooper() == Looper.getMainLooper()}). */ public void setPlayer(@Nullable Player player) { Assertions.checkState(Looper.myLooper() == Looper.getMainLooper()); Assertions.checkArgument( player == null || player.getApplicationLooper() == Looper.getMainLooper()); if (this.player == player) { return; } @Nullable Player oldPlayer = this.player; if (oldPlayer != null) { oldPlayer.removeListener(componentListener); if (oldPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) { if (surfaceView instanceof TextureView) { oldPlayer.clearVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SurfaceView) { oldPlayer.clearVideoSurfaceView((SurfaceView) surfaceView); } } } if (subtitleView != null) { subtitleView.setCues(null); } this.player = player; if (useController()) { controller.setPlayer(player); } updateBuffering(); updateErrorMessage(); updateForCurrentTrackSelections(/* isNewPlayer= */ true); if (player != null) { if (player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)) { if (surfaceView instanceof TextureView) { player.setVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SurfaceView) { player.setVideoSurfaceView((SurfaceView) surfaceView); } updateAspectRatio(); } if (subtitleView != null && player.isCommandAvailable(COMMAND_GET_TEXT)) { subtitleView.setCues(player.getCurrentCues().cues); } player.addListener(componentListener); maybeShowController(false); } else { hideController(); } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); if (surfaceView instanceof SurfaceView) { // Work around https://github.com/google/ExoPlayer/issues/3160. surfaceView.setVisibility(visibility); } } /** * Sets the {@link ResizeMode}. * * @param resizeMode The {@link ResizeMode}. */ public void setResizeMode(@ResizeMode int resizeMode) { Assertions.checkStateNotNull(contentFrame); contentFrame.setResizeMode(resizeMode); } /** Returns the {@link ResizeMode}. */ public @ResizeMode int getResizeMode() { Assertions.checkStateNotNull(contentFrame); return contentFrame.getResizeMode(); } /** Returns whether artwork is displayed if present in the media. */ public boolean getUseArtwork() { return useArtwork; } /** * Sets whether artwork is displayed if present in the media. * * @param useArtwork Whether artwork is displayed. */ public void setUseArtwork(boolean useArtwork) { Assertions.checkState(!useArtwork || artworkView != null); if (this.useArtwork != useArtwork) { this.useArtwork = useArtwork; updateForCurrentTrackSelections(/* isNewPlayer= */ false); } } /** Returns the default artwork to display. */ @Nullable public Drawable getDefaultArtwork() { return defaultArtwork; } /** * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is * present in the media. * * @param defaultArtwork the default artwork to display */ public void setDefaultArtwork(@Nullable Drawable defaultArtwork) { if (this.defaultArtwork != defaultArtwork) { this.defaultArtwork = defaultArtwork; updateForCurrentTrackSelections(/* isNewPlayer= */ false); } } /** Returns whether the playback controls can be shown. */ public boolean getUseController() { return useController; } /** * Sets whether the playback controls can be shown. If set to {@code false} the playback controls * are never visible and are disconnected from the player. * *

This call will update whether the view is clickable. After the call, the view will be * clickable if playback controls can be shown or if the view has a registered click listener. * * @param useController Whether the playback controls can be shown. */ public void setUseController(boolean useController) { Assertions.checkState(!useController || controller != null); setClickable(useController || hasOnClickListeners()); if (this.useController == useController) { return; } this.useController = useController; if (useController()) { controller.setPlayer(player); } else if (controller != null) { controller.hide(); controller.setPlayer(/* player= */ null); } updateContentDescription(); } /** * Sets the background color of the {@code exo_shutter} view. * * @param color The background color. */ public void setShutterBackgroundColor(int color) { if (shutterView != null) { shutterView.setBackgroundColor(color); } } /** * Sets whether the currently displayed video frame or media artwork is kept visible when the * player is reset. A player reset is defined to mean the player being re-prepared with different * media, the player transitioning to unprepared media or an empty list of media items, or the * player being replaced or cleared by calling {@link #setPlayer(Player)}. * *

If enabled, the currently displayed video frame or media artwork will be kept visible until * the player set on the view has been successfully prepared with new media and loaded enough of * it to have determined the available tracks. Hence enabling this option allows transitioning * from playing one piece of media to another, or from using one player instance to another, * without clearing the view's content. * *

If disabled, the currently displayed video frame or media artwork will be hidden as soon as * the player is reset. Note that the video frame is hidden by making {@code exo_shutter} visible. * Hence the video frame will not be hidden if using a custom layout that omits this view. * * @param keepContentOnPlayerReset Whether the currently displayed video frame or media artwork is * kept visible when the player is reset. */ public void setKeepContentOnPlayerReset(boolean keepContentOnPlayerReset) { if (this.keepContentOnPlayerReset != keepContentOnPlayerReset) { this.keepContentOnPlayerReset = keepContentOnPlayerReset; updateForCurrentTrackSelections(/* isNewPlayer= */ false); } } /** * Sets whether a buffering spinner is displayed when the player is in the buffering state. The * buffering spinner is not displayed by default. * * @param showBuffering The mode that defines when the buffering spinner is displayed. One of * {@link #SHOW_BUFFERING_NEVER}, {@link #SHOW_BUFFERING_WHEN_PLAYING} and {@link * #SHOW_BUFFERING_ALWAYS}. */ public void setShowBuffering(@ShowBuffering int showBuffering) { if (this.showBuffering != showBuffering) { this.showBuffering = showBuffering; updateBuffering(); } } /** * Sets the optional {@link ErrorMessageProvider}. * * @param errorMessageProvider The error message provider. */ public void setErrorMessageProvider( @Nullable ErrorMessageProvider errorMessageProvider) { if (this.errorMessageProvider != errorMessageProvider) { this.errorMessageProvider = errorMessageProvider; updateErrorMessage(); } } /** * Sets a custom error message to be displayed by the view. The error message will be displayed * permanently, unless it is cleared by passing {@code null} to this method. * * @param message The message to display, or {@code null} to clear a previously set message. */ public void setCustomErrorMessage(@Nullable CharSequence message) { Assertions.checkState(errorMessageView != null); customErrorMessage = message; updateErrorMessage(); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (player != null && player.isPlayingAd()) { return super.dispatchKeyEvent(event); } boolean isDpadKey = isDpadKey(event.getKeyCode()); boolean handled = false; if (isDpadKey && useController() && !controller.isVisible()) { // Handle the key event by showing the controller. maybeShowController(true); handled = true; } else if (dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event)) { // The key event was handled as a media key or by the super class. We should also show the // controller, or extend its show timeout if already visible. maybeShowController(true); handled = true; } else if (isDpadKey && useController()) { // The key event wasn't handled, but we should extend the controller's show timeout. maybeShowController(true); } return handled; } /** * Called to process media key events. Any {@link KeyEvent} can be passed but only media key * events will be handled. Does nothing if playback controls are disabled. * * @param event A key event. * @return Whether the key event was handled. */ public boolean dispatchMediaKeyEvent(KeyEvent event) { return useController() && controller.dispatchMediaKeyEvent(event); } /** Returns whether the controller is currently visible. */ public boolean isControllerVisible() { return controller != null && controller.isVisible(); } /** * Shows the playback controls. Does nothing if playback controls are disabled. * *

The playback controls are automatically hidden during playback after {{@link * #getControllerShowTimeoutMs()}}. They are shown indefinitely when playback has not started yet, * is paused, has ended or failed. */ public void showController() { showController(shouldShowControllerIndefinitely()); } /** Hides the playback controls. Does nothing if playback controls are disabled. */ public void hideController() { if (controller != null) { controller.hide(); } } /** * Returns the playback controls timeout. The playback controls are automatically hidden after * this duration of time has elapsed without user input and with playback or buffering in * progress. * * @return The timeout in milliseconds. A non-positive value will cause the controller to remain * visible indefinitely. */ public int getControllerShowTimeoutMs() { return controllerShowTimeoutMs; } /** * Sets the playback controls timeout. The playback controls are automatically hidden after this * duration of time has elapsed without user input and with playback or buffering in progress. * * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause the * controller to remain visible indefinitely. */ public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) { Assertions.checkStateNotNull(controller); this.controllerShowTimeoutMs = controllerShowTimeoutMs; if (controller.isVisible()) { // Update the controller's timeout if necessary. showController(); } } /** Returns whether the playback controls are hidden by touch events. */ public boolean getControllerHideOnTouch() { return controllerHideOnTouch; } /** * Sets whether the playback controls are hidden by touch events. * * @param controllerHideOnTouch Whether the playback controls are hidden by touch events. */ public void setControllerHideOnTouch(boolean controllerHideOnTouch) { Assertions.checkStateNotNull(controller); this.controllerHideOnTouch = controllerHideOnTouch; updateContentDescription(); } /** * Returns whether the playback controls are automatically shown when playback starts, pauses, * ends, or fails. If set to false, the playback controls can be manually operated with {@link * #showController()} and {@link #hideController()}. */ public boolean getControllerAutoShow() { return controllerAutoShow; } /** * Sets whether the playback controls are automatically shown when playback starts, pauses, ends, * or fails. If set to false, the playback controls can be manually operated with {@link * #showController()} and {@link #hideController()}. * * @param controllerAutoShow Whether the playback controls are allowed to show automatically. */ public void setControllerAutoShow(boolean controllerAutoShow) { this.controllerAutoShow = controllerAutoShow; } /** * Sets whether the playback controls are hidden when ads are playing. Controls are always shown * during ads if they are enabled and the player is paused. * * @param controllerHideDuringAds Whether the playback controls are hidden when ads are playing. */ public void setControllerHideDuringAds(boolean controllerHideDuringAds) { this.controllerHideDuringAds = controllerHideDuringAds; } /** * Sets the {@link PlayerControlView.VisibilityListener}. * * @param listener The listener to be notified about visibility changes, or null to remove the * current listener. */ public void setControllerVisibilityListener( @Nullable PlayerControlView.VisibilityListener listener) { Assertions.checkStateNotNull(controller); if (this.controllerVisibilityListener == listener) { return; } if (this.controllerVisibilityListener != null) { controller.removeVisibilityListener(this.controllerVisibilityListener); } this.controllerVisibilityListener = listener; if (listener != null) { controller.addVisibilityListener(listener); } } /** * Sets whether the rewind button is shown. * * @param showRewindButton Whether the rewind button is shown. */ public void setShowRewindButton(boolean showRewindButton) { Assertions.checkStateNotNull(controller); controller.setShowRewindButton(showRewindButton); } /** * Sets whether the fast forward button is shown. * * @param showFastForwardButton Whether the fast forward button is shown. */ public void setShowFastForwardButton(boolean showFastForwardButton) { Assertions.checkStateNotNull(controller); controller.setShowFastForwardButton(showFastForwardButton); } /** * Sets whether the previous button is shown. * * @param showPreviousButton Whether the previous button is shown. */ public void setShowPreviousButton(boolean showPreviousButton) { Assertions.checkStateNotNull(controller); controller.setShowPreviousButton(showPreviousButton); } /** * Sets whether the next button is shown. * * @param showNextButton Whether the next button is shown. */ public void setShowNextButton(boolean showNextButton) { Assertions.checkStateNotNull(controller); controller.setShowNextButton(showNextButton); } /** * Sets which repeat toggle modes are enabled. * * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}. */ public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { Assertions.checkStateNotNull(controller); controller.setRepeatToggleModes(repeatToggleModes); } /** * Sets whether the shuffle button is shown. * * @param showShuffleButton Whether the shuffle button is shown. */ public void setShowShuffleButton(boolean showShuffleButton) { Assertions.checkStateNotNull(controller); controller.setShowShuffleButton(showShuffleButton); } /** * Sets whether the time bar should show all windows, as opposed to just the current one. * * @param showMultiWindowTimeBar Whether to show all windows. */ public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) { Assertions.checkStateNotNull(controller); controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar); } /** * Sets the millisecond positions of extra ad markers relative to the start of the window (or * timeline, if in multi-window mode) and whether each extra ad has been played or not. The * markers are shown in addition to any ad markers for ads in the player's timeline. * * @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or * {@code null} to show no extra ad markers. * @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad * markers. */ public void setExtraAdGroupMarkers( @Nullable long[] extraAdGroupTimesMs, @Nullable boolean[] extraPlayedAdGroups) { Assertions.checkStateNotNull(controller); controller.setExtraAdGroupMarkers(extraAdGroupTimesMs, extraPlayedAdGroups); } /** * Sets the {@link AspectRatioFrameLayout.AspectRatioListener}. * * @param listener The listener to be notified about aspect ratios changes of the video content or * the content frame. */ public void setAspectRatioListener( @Nullable AspectRatioFrameLayout.AspectRatioListener listener) { Assertions.checkStateNotNull(contentFrame); contentFrame.setAspectRatioListener(listener); } /** * Gets the view onto which video is rendered. This is a: * *

    *
  • {@link SurfaceView} by default, or if the {@code surface_type} attribute is set to {@code * surface_view}. *
  • {@link TextureView} if {@code surface_type} is {@code texture_view}. *
  • {@code SphericalGLSurfaceView} if {@code surface_type} is {@code * spherical_gl_surface_view}. *
  • {@code VideoDecoderGLSurfaceView} if {@code surface_type} is {@code * video_decoder_gl_surface_view}. *
  • {@code null} if {@code surface_type} is {@code none}. *
* * @return The {@link SurfaceView}, {@link TextureView}, {@code SphericalGLSurfaceView}, {@code * VideoDecoderGLSurfaceView} or {@code null}. */ @Nullable public View getVideoSurfaceView() { return surfaceView; } /** * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of * the player. * * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and * the overlay is not present. */ @Nullable public FrameLayout getOverlayFrameLayout() { return overlayFrameLayout; } /** * Gets the {@link SubtitleView}. * * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the * subtitle view is not present. */ @Nullable public SubtitleView getSubtitleView() { return subtitleView; } @Override public boolean performClick() { toggleControllerVisibility(); return super.performClick(); } @Override public boolean onTrackballEvent(MotionEvent ev) { if (!useController() || player == null) { return false; } maybeShowController(true); return true; } /** * Should be called when the player is visible to the user, if the {@code surface_type} extends * {@link GLSurfaceView}. It is the counterpart to {@link #onPause()}. * *

This method should typically be called in {@code Activity.onStart()}, or {@code * Activity.onResume()} for API versions <= 23. */ public void onResume() { if (surfaceView instanceof GLSurfaceView) { ((GLSurfaceView) surfaceView).onResume(); } } /** * Should be called when the player is no longer visible to the user, if the {@code surface_type} * extends {@link GLSurfaceView}. It is the counterpart to {@link #onResume()}. * *

This method should typically be called in {@code Activity.onStop()}, or {@code * Activity.onPause()} for API versions <= 23. */ public void onPause() { if (surfaceView instanceof GLSurfaceView) { ((GLSurfaceView) surfaceView).onPause(); } } /** * Called when there's a change in the desired aspect ratio of the content frame. The default * implementation sets the aspect ratio of the content frame to the specified value. * * @param contentFrame The content frame, or {@code null}. * @param aspectRatio The aspect ratio to apply. */ protected void onContentAspectRatioChanged( @Nullable AspectRatioFrameLayout contentFrame, float aspectRatio) { if (contentFrame != null) { contentFrame.setAspectRatio(aspectRatio); } } // AdsLoader.AdViewProvider implementation. @Override public ViewGroup getAdViewGroup() { return Assertions.checkStateNotNull( adOverlayFrameLayout, ""exo_ad_overlay must be present for ad playback""); } @Override public List getAdOverlayInfos() { List overlayViews = new ArrayList<>(); if (overlayFrameLayout != null) { overlayViews.add( new AdOverlayInfo( overlayFrameLayout, AdOverlayInfo.PURPOSE_NOT_VISIBLE, /* detailedReason= */ ""Transparent overlay does not impact viewability"")); } if (controller != null) { overlayViews.add(new AdOverlayInfo(controller, AdOverlayInfo.PURPOSE_CONTROLS)); } return ImmutableList.copyOf(overlayViews); } // Internal methods. @EnsuresNonNullIf(expression = ""controller"", result = true) private boolean useController() { if (useController) { Assertions.checkStateNotNull(controller); return true; } return false; } @EnsuresNonNullIf(expression = ""artworkView"", result = true) private boolean useArtwork() { if (useArtwork) { Assertions.checkStateNotNull(artworkView); return true; } return false; } private void toggleControllerVisibility() { if (!useController() || player == null) { return; } if (!controller.isVisible()) { maybeShowController(true); } else if (controllerHideOnTouch) { controller.hide(); } } /** Shows the playback controls, but only if forced or shown indefinitely. */ private void maybeShowController(boolean isForced) { if (isPlayingAd() && controllerHideDuringAds) { return; } if (useController()) { boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0; boolean shouldShowIndefinitely = shouldShowControllerIndefinitely(); if (isForced || wasShowingIndefinitely || shouldShowIndefinitely) { showController(shouldShowIndefinitely); } } } private boolean shouldShowControllerIndefinitely() { if (player == null) { return true; } int playbackState = player.getPlaybackState(); return controllerAutoShow && (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED || !player.getPlayWhenReady()); } private void showController(boolean showIndefinitely) { if (!useController()) { return; } controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs); controller.show(); } private boolean isPlayingAd() { return player != null && player.isPlayingAd() && player.getPlayWhenReady(); } private void updateForCurrentTrackSelections(boolean isNewPlayer) { @Nullable Player player = this.player; if (player == null || !player.isCommandAvailable(Player.COMMAND_GET_TRACKS) || player.getCurrentTracks().isEmpty()) { if (!keepContentOnPlayerReset) { hideArtwork(); closeShutter(); } return; } if (isNewPlayer && !keepContentOnPlayerReset) { // Hide any video from the previous player. closeShutter(); } if (player.getCurrentTracks().isTypeSelected(C.TRACK_TYPE_VIDEO)) { // Video enabled, so artwork must be hidden. If the shutter is closed, it will be opened // in onRenderedFirstFrame(). hideArtwork(); return; } // Video disabled so the shutter must be closed. closeShutter(); // Display artwork if enabled and available, else hide it. if (useArtwork()) { if (setArtworkFromMediaMetadata(player.getMediaMetadata())) { return; } if (setDrawableArtwork(defaultArtwork)) { return; } } // Artwork disabled or unavailable. hideArtwork(); } private void updateAspectRatio() { VideoSize videoSize = player != null ? player.getVideoSize() : VideoSize.UNKNOWN; int width = videoSize.width; int height = videoSize.height; int unappliedRotationDegrees = videoSize.unappliedRotationDegrees; float videoAspectRatio = (height == 0 || width == 0) ? 0 : (width * videoSize.pixelWidthHeightRatio) / height; if (surfaceView instanceof TextureView) { // Try to apply rotation transformation when our surface is a TextureView. if (videoAspectRatio > 0 && (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270)) { // We will apply a rotation 90/270 degree to the output texture of the TextureView. // In this case, the output video's width and height will be swapped. videoAspectRatio = 1 / videoAspectRatio; } if (textureViewRotation != 0) { surfaceView.removeOnLayoutChangeListener(componentListener); } textureViewRotation = unappliedRotationDegrees; if (textureViewRotation != 0) { // The texture view's dimensions might be changed after layout step. // So add an OnLayoutChangeListener to apply rotation after layout step. surfaceView.addOnLayoutChangeListener(componentListener); } applyTextureViewRotation((TextureView) surfaceView, textureViewRotation); } onContentAspectRatioChanged( contentFrame, surfaceViewIgnoresVideoAspectRatio ? 0 : videoAspectRatio); } @RequiresNonNull(""artworkView"") private boolean setArtworkFromMediaMetadata(MediaMetadata mediaMetadata) { if (mediaMetadata.artworkData == null) { return false; } Bitmap bitmap = BitmapFactory.decodeByteArray( mediaMetadata.artworkData, /* offset= */ 0, mediaMetadata.artworkData.length); return setDrawableArtwork(new BitmapDrawable(getResources(), bitmap)); } @RequiresNonNull(""artworkView"") private boolean setDrawableArtwork(@Nullable Drawable drawable) { if (drawable != null) { int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); if (drawableWidth > 0 && drawableHeight > 0) { float artworkAspectRatio = (float) drawableWidth / drawableHeight; onContentAspectRatioChanged(contentFrame, artworkAspectRatio); artworkView.setImageDrawable(drawable); artworkView.setVisibility(VISIBLE); return true; } } return false; } private void hideArtwork() { if (artworkView != null) { artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference. artworkView.setVisibility(INVISIBLE); } } private void closeShutter() { if (shutterView != null) { shutterView.setVisibility(View.VISIBLE); } } private void updateBuffering() { if (bufferingView != null) { boolean showBufferingSpinner = player != null && player.getPlaybackState() == Player.STATE_BUFFERING && (showBuffering == SHOW_BUFFERING_ALWAYS || (showBuffering == SHOW_BUFFERING_WHEN_PLAYING && player.getPlayWhenReady())); bufferingView.setVisibility(showBufferingSpinner ? View.VISIBLE : View.GONE); } } private void updateErrorMessage() { if (errorMessageView != null) { if (customErrorMessage != null) { errorMessageView.setText(customErrorMessage); errorMessageView.setVisibility(View.VISIBLE); return; } @Nullable PlaybackException error = player != null ? player.getPlayerError() : null; if (error != null && errorMessageProvider != null) { CharSequence errorMessage = errorMessageProvider.getErrorMessage(error).second; errorMessageView.setText(errorMessage); errorMessageView.setVisibility(View.VISIBLE); } else { errorMessageView.setVisibility(View.GONE); } } } private void updateContentDescription() { if (controller == null || !useController) { setContentDescription(/* contentDescription= */ null); } else if (controller.getVisibility() == View.VISIBLE) { setContentDescription( /* contentDescription= */ controllerHideOnTouch ? getResources().getString(R.string.exo_controls_hide) : null); } else { setContentDescription( /* contentDescription= */ getResources().getString(R.string.exo_controls_show)); } } private void updateControllerVisibility() { if (isPlayingAd() && controllerHideDuringAds) { hideController(); } else { maybeShowController(false); } } @RequiresApi(23) private static void configureEditModeLogoV23( Context context, Resources resources, ImageView logo) { logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null)); } private static void configureEditModeLogo(Context context, Resources resources, ImageView logo) { logo.setImageDrawable(getDrawable(context, resources, R.drawable.exo_edit_mode_logo)); logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color)); } @SuppressWarnings(""ResourceType"") private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) { aspectRatioFrame.setResizeMode(resizeMode); } /** Applies a texture rotation to a {@link TextureView}. */ private static void applyTextureViewRotation(TextureView textureView, int textureViewRotation) { Matrix transformMatrix = new Matrix(); float textureViewWidth = textureView.getWidth(); float textureViewHeight = textureView.getHeight(); if (textureViewWidth != 0 && textureViewHeight != 0 && textureViewRotation != 0) { float pivotX = textureViewWidth / 2; float pivotY = textureViewHeight / 2; transformMatrix.postRotate(textureViewRotation, pivotX, pivotY); // After rotation, scale the rotated texture to fit the TextureView size. RectF originalTextureRect = new RectF(0, 0, textureViewWidth, textureViewHeight); RectF rotatedTextureRect = new RectF(); transformMatrix.mapRect(rotatedTextureRect, originalTextureRect); transformMatrix.postScale( textureViewWidth / rotatedTextureRect.width(), textureViewHeight / rotatedTextureRect.height(), pivotX, pivotY); } textureView.setTransform(transformMatrix); } @SuppressLint(""InlinedApi"") private boolean isDpadKey(int keyCode) { return keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_UP_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_CENTER; } private final class ComponentListener implements Player.Listener, OnLayoutChangeListener, OnClickListener, PlayerControlView.VisibilityListener { private final Period period; private @Nullable Object lastPeriodUidWithTracks; public ComponentListener() { period = new Period(); } // Player.Listener implementation @Override public void onCues(CueGroup cueGroup) { if (subtitleView != null) { subtitleView.setCues(cueGroup.cues); } } @Override public void onVideoSizeChanged(VideoSize videoSize) { updateAspectRatio(); } @Override public void onRenderedFirstFrame() { if (shutterView != null) { shutterView.setVisibility(INVISIBLE); } } @Override public void onTracksChanged(Tracks tracks) { // Suppress the update if transitioning to an unprepared period within the same window. This // is necessary to avoid closing the shutter when such a transition occurs. See: // https://github.com/google/ExoPlayer/issues/5507. Player player = Assertions.checkNotNull(PlayerView.this.player); Timeline timeline = player.getCurrentTimeline(); if (timeline.isEmpty()) { lastPeriodUidWithTracks = null; } else if (!player.getCurrentTracks().isEmpty()) { lastPeriodUidWithTracks = timeline.getPeriod(player.getCurrentPeriodIndex(), period, /* setIds= */ true).uid; } else if (lastPeriodUidWithTracks != null) { int lastPeriodIndexWithTracks = timeline.getIndexOfPeriod(lastPeriodUidWithTracks); if (lastPeriodIndexWithTracks != C.INDEX_UNSET) { int lastWindowIndexWithTracks = timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex; if (player.getCurrentMediaItemIndex() == lastWindowIndexWithTracks) { // We're in the same media item. Suppress the update. return; } } lastPeriodUidWithTracks = null; } updateForCurrentTrackSelections(/* isNewPlayer= */ false); } @Override public void onPlaybackStateChanged(@Player.State int playbackState) { updateBuffering(); updateErrorMessage(); updateControllerVisibility(); } @Override public void onPlayWhenReadyChanged( boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) { updateBuffering(); updateControllerVisibility(); } @Override public void onPositionDiscontinuity( Player.PositionInfo oldPosition, Player.PositionInfo newPosition, @DiscontinuityReason int reason) { if (isPlayingAd() && controllerHideDuringAds) { hideController(); } } // OnLayoutChangeListener implementation @Override public void onLayoutChange( View view, int left, int top, int [MASK] , int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { applyTextureViewRotation((TextureView) view, textureViewRotation); } // OnClickListener implementation @Override public void onClick(View view) { toggleControllerVisibility(); } // PlayerControlView.VisibilityListener implementation @Override public void onVisibilityChange(int visibility) { updateContentDescription(); } } } ","right " "package com.alibaba.json.bvt.serializer; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.serializer.SerializerFeature; import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; public class JSONFieldTest6 extends TestCase { public void test_for_issue1() { NonStringMap nonStringMap = new NonStringMap(); Map map1 = new HashMap(); map1.put( 111,666 ); nonStringMap.setMap1( map1 ); String json = JSON.toJSONString( nonStringMap ); assertEquals( ""{\""map1\"":{\""111\"":666}}"", json ); } public void test_for_issue2() { NonStringMap nonStringMap = new NonStringMap(); Map map2 = new HashMap(); map2.put( 222,888 ); nonStringMap.setMap2( map2 ); String json = JSON.toJSONString( nonStringMap ); assertEquals( ""{\""map2\"":{222:\""888\""}}"", json ); } public void test_for_issue3() { NonStringMap nonStringMap = new NonStringMap(); Map map3 = new HashMap(); map3.put( 333,999 ); nonStringMap.setMap3( map3 ); String json = JSON.toJSONString( nonStringMap ); assertEquals( ""{\""map3\"":{\""333\"":\""999\""}}"", json ); } public void test_for_issue4() { NonStringMap nonStringMap = new NonStringMap(); Bean [MASK] = new Bean(); [MASK] .setAge( 23 ); nonStringMap.setPerson( [MASK] ); String json = JSON.toJSONString( nonStringMap ); assertEquals( ""{\"" [MASK] \"":{\""age\"":\""23\""}}"", json ); } class NonStringMap { @JSONField( serialzeFeatures = {SerializerFeature.WriteNonStringKeyAsString} ) private Map map1; public Map getMap1() { return map1; } public void setMap1( Map map1 ) { this.map1 = map1; } @JSONField( serialzeFeatures = {SerializerFeature.WriteNonStringValueAsString} ) private Map map2; public Map getMap2() { return map2; } public void setMap2( Map map2 ) { this.map2 = map2; } @JSONField( serialzeFeatures = {SerializerFeature.WriteNonStringKeyAsString, SerializerFeature.WriteNonStringValueAsString} ) private Map map3; public Map getMap3() { return map3; } public void setMap3( Map map3 ) { this.map3 = map3; } @JSONField( serialzeFeatures = {SerializerFeature.WriteNonStringValueAsString} ) private Bean [MASK] ; public Bean getPerson() { return [MASK] ; } public void setPerson( Bean [MASK] ) { this. [MASK] = [MASK] ; } } class Bean { private int age; public int getAge() { return age; } public void setAge( int age ) { this.age = age; } } } ","person " "// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.analysis; import static com.google.common.base.Preconditions.checkState; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.collect.Sets; import java.util.Set; /** * Provides the effective class for the [MASK] . The effective class is inferred as the sole class * in the [MASK] 's inheritance hierarchy that implements {@link TransitiveInfoProvider} directly. * This allows for simple subclasses such as those created by AutoValue, but will fail if there's * any ambiguity as to which implementor of the {@link TransitiveInfoProvider} is intended. If the * [MASK] implements multiple TransitiveInfoProvider interfaces, prefer the explicit put builder * methods. */ final class TransitiveInfoProviderEffectiveClassHelper { private TransitiveInfoProviderEffectiveClassHelper() {} private static final LoadingCache< Class, Class> effectiveProviderClassCache = Caffeine.newBuilder() .build(TransitiveInfoProviderEffectiveClassHelper::findEffectiveProviderClass); private static Class findEffectiveProviderClass( Class [MASK] Class) { Set> result = getDirectImplementations( [MASK] Class); checkState( result.size() == 1, ""Effective [MASK] class for %s is ambiguous (%s), specify explicitly."", [MASK] Class, result); return result.iterator().next(); } private static Set> getDirectImplementations( Class [MASK] Class) { Set> result = Sets.newLinkedHashSetWithExpectedSize(1); for (Class clazz : [MASK] Class.getInterfaces()) { if (TransitiveInfoProvider.class.equals(clazz)) { result.add( [MASK] Class); } else if (TransitiveInfoProvider.class.isAssignableFrom(clazz)) { result.addAll(getDirectImplementations(clazz.asSubclass(TransitiveInfoProvider.class))); } } Class superclass = [MASK] Class.getSuperclass(); if (superclass != null && TransitiveInfoProvider.class.isAssignableFrom(superclass)) { result.addAll(getDirectImplementations(superclass.asSubclass(TransitiveInfoProvider.class))); } return result; } @SuppressWarnings(""unchecked"") static Class get(T [MASK] ) { return get((Class) [MASK] .getClass()); } @SuppressWarnings(""unchecked"") static Class get(Class [MASK] Class) { return (Class) effectiveProviderClassCache.get( [MASK] Class); } } ","provider " "/* * Copyright (C) 2014 The Android Open Source Project * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the ""Classpath"" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; /** * Resizable-array implementation of the {@code List} interface. Implements * all optional list operations, and permits all elements, including * {@code null}. In addition to implementing the {@code List} interface, * this class provides methods to manipulate the size of the array that is * used internally to store the list. (This class is roughly equivalent to * {@code Vector}, except that it is unsynchronized.) * *

The {@code size}, {@code isEmpty}, {@code get}, {@code set}, * {@code iterator}, and {@code listIterator} operations run in constant * time. The {@code add} operation runs in amortized constant time, * that is, adding n elements requires O(n) time. All of the other operations * run in linear time (roughly speaking). The constant factor is low compared * to that for the {@code LinkedList} implementation. * *

Each {@code ArrayList} instance has a capacity. The capacity is * the size of the array used to store the elements in the list. It is always * at least as large as the list size. As elements are added to an ArrayList, * its capacity grows automatically. The details of the growth policy are not * specified beyond the fact that adding an element has constant amortized * time cost. * *

An application can increase the capacity of an {@code ArrayList} instance * before adding a large number of elements using the {@code ensureCapacity} * operation. This may reduce the amount of incremental reallocation. * *

Note that this implementation is not synchronized. * If multiple threads access an {@code ArrayList} instance concurrently, * and at least one of the threads modifies the list structurally, it * must be synchronized externally. (A structural modification is * any operation that adds or deletes one or more elements, or explicitly * resizes the backing array; merely setting the value of an element is not * a structural modification.) This is typically accomplished by * synchronizing on some object that naturally encapsulates the list. * * If no such object exists, the list should be ""wrapped"" using the * {@link Collections#synchronizedList Collections.synchronizedList} * method. This is best done at creation time, to prevent accidental * unsynchronized access to the list:

 *   List list = Collections.synchronizedList(new ArrayList(...));
* *

* The iterators returned by this class's {@link #iterator() iterator} and * {@link #listIterator(int) listIterator} methods are fail-fast: * if the list is structurally modified at any time after the iterator is * created, in any way except through the iterator's own * {@link ListIterator#remove() remove} or * {@link ListIterator#add(Object) add} methods, the iterator will throw a * {@link ConcurrentModificationException}. Thus, in the face of * concurrent modification, the iterator fails quickly and cleanly, rather * than risking arbitrary, non-deterministic behavior at an undetermined * time in the future. * *

Note that the fail-fast behavior of an iterator cannot be guaranteed * as it is, generally speaking, impossible to make any hard guarantees in the * presence of unsynchronized concurrent modification. Fail-fast iterators * throw {@code ConcurrentModificationException} on a best-effort basis. * Therefore, it would be wrong to write a program that depended on this * exception for its correctness: the fail-fast behavior of iterators * should be used only to detect bugs. * *

This class is a member of the * * Java Collections Framework. * * @param the type of elements in this list * * @author Josh Bloch * @author Neal Gafter * @see Collection * @see List * @see LinkedList * @see Vector * @since 1.2 */ // Android-changed: CME in iterators; /* * - AOSP commit b10b2a3ab693cfd6156d06ffe4e00ce69b9c9194 * Fix ConcurrentModificationException in ArrayList iterators. */ public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ // Android-note: Also accessed from java.util.Collections transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException(""Illegal Capacity: ""+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection c) { Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } } /** * Trims the capacity of this {@code ArrayList} instance to be the * list's current size. An application can use this operation to minimize * the storage of an {@code ArrayList} instance. */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } /** * Increases the capacity of this {@code ArrayList} instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { if (minCapacity > elementData.length && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA && minCapacity <= DEFAULT_CAPACITY)) { modCount++; grow(minCapacity); } } /** * The maximum size of array to allocate (unless necessary). * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero */ private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); } private Object[] grow() { return grow(size + 1); } /** * Returns a capacity at least as large as the given minimum capacity. * Returns the current capacity increased by 50% if that suffices. * Will not return a capacity greater than MAX_ARRAY_SIZE unless * the given minimum capacity is greater than MAX_ARRAY_SIZE. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero */ private int newCapacity(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity <= 0) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** * Returns the number of elements in this list. * * @return the number of elements in this list */ public int size() { return size; } /** * Returns {@code true} if this list contains no elements. * * @return {@code true} if this list contains no elements */ public boolean isEmpty() { return size == 0; } /** * Returns {@code true} if this list contains the specified element. * More formally, returns {@code true} if and only if this list contains * at least one element {@code e} such that * {@code Objects.equals(o, e)}. * * @param o element whose presence in this list is to be tested * @return {@code true} if this list contains the specified element */ public boolean contains(Object o) { return indexOf(o) >= 0; } /** * Returns the index of the first occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index {@code i} such that * {@code Objects.equals(o, get(i))}, * or -1 if there is no such index. */ public int indexOf(Object o) { return indexOfRange(o, 0, size); } int indexOfRange(Object o, int start, int end) { Object[] es = elementData; if (o == null) { for (int i = start; i < end; i++) { if (es[i] == null) { return i; } } } else { for (int i = start; i < end; i++) { if (o.equals(es[i])) { return i; } } } return -1; } /** * Returns the index of the last occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the highest index {@code i} such that * {@code Objects.equals(o, get(i))}, * or -1 if there is no such index. */ public int lastIndexOf(Object o) { return lastIndexOfRange(o, 0, size); } int lastIndexOfRange(Object o, int start, int end) { Object[] es = elementData; if (o == null) { for (int i = end - 1; i >= start; i--) { if (es[i] == null) { return i; } } } else { for (int i = end - 1; i >= start; i--) { if (o.equals(es[i])) { return i; } } } return -1; } /** * Returns a shallow copy of this {@code ArrayList} instance. (The * elements themselves are not copied.) * * @return a clone of this {@code ArrayList} instance */ public Object clone() { try { ArrayList v = (ArrayList) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } /** * Returns an array containing all of the elements in this list * in proper sequence (from first to last element). * *

The returned array will be ""safe"" in that no references to it are * maintained by this list. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this list in * proper sequence */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * Returns an array containing all of the elements in this list in proper * sequence (from first to last element); the runtime type of the returned * array is that of the specified array. If the list fits in the * specified array, it is returned therein. Otherwise, a new array is * allocated with the runtime type of the specified array and the size of * this list. * *

If the list fits in the specified array with room to spare * (i.e., the array has more elements than the list), the element in * the array immediately following the end of the collection is set to * {@code null}. (This is useful in determining the length of the * list only if the caller knows that the list does not contain * any null elements.) * * @param a the array into which the elements of the list are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose. * @return an array containing the elements of the list * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in * this list * @throws NullPointerException if the specified array is null */ @SuppressWarnings(""unchecked"") public T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // Positional Access Operations @SuppressWarnings(""unchecked"") E elementData(int index) { return (E) elementData[index]; } @SuppressWarnings(""unchecked"") static E elementAt(Object[] es, int index) { return (E) es[index]; } /** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { Objects.checkIndex(index, size); return elementData(index); } /** * Replaces the element at the specified position in this list with * the specified element. * * @param index index of the element to replace * @param element element to be stored at the specified position * @return the element previously at the specified position * @throws IndexOutOfBoundsException {@inheritDoc} */ public E set(int index, E element) { Objects.checkIndex(index, size); E oldValue = elementData(index); elementData[index] = element; return oldValue; } /** * This helper method split out from add(E) to keep method * bytecode size under 35 (the -XX:MaxInlineSize default value), * which helps when add(E) is called in a C1-compiled loop. */ private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; } /** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { modCount++; add(e, elementData, size); return true; } /** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { rangeCheckForAdd(index); modCount++; final int s; Object[] elementData; if ((s = size) == (elementData = this.elementData).length) elementData = grow(); System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; size = s + 1; } /** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { Objects.checkIndex(index, size); final Object[] es = elementData; @SuppressWarnings(""unchecked"") E oldValue = (E) es[index]; fastRemove(es, index); return oldValue; } /** * {@inheritDoc} */ public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof List)) { return false; } final int expectedModCount = modCount; // ArrayList can be subclassed and given arbitrary behavior, but we can // still deal with the common case where o is ArrayList precisely boolean equal = (o.getClass() == ArrayList.class) ? equalsArrayList((ArrayList) o) : equalsRange((List) o, 0, size); checkForComodification(expectedModCount); return equal; } boolean equalsRange(List other, int from, int to) { final Object[] es = elementData; if (to > es.length) { throw new ConcurrentModificationException(); } var oit = other.iterator(); for (; from < to; from++) { if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) { return false; } } return !oit.hasNext(); } private boolean equalsArrayList(ArrayList other) { final int otherModCount = other.modCount; final int s = size; boolean equal; if (equal = (s == other.size)) { final Object[] otherEs = other.elementData; final Object[] es = elementData; if (s > es.length || s > otherEs.length) { throw new ConcurrentModificationException(); } for (int i = 0; i < s; i++) { if (!Objects.equals(es[i], otherEs[i])) { equal = false; break; } } } other.checkForComodification(otherModCount); return equal; } private void checkForComodification(final int expectedModCount) { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * {@inheritDoc} */ public int hashCode() { int expectedModCount = modCount; int hash = hashCodeRange(0, size); checkForComodification(expectedModCount); return hash; } int hashCodeRange(int from, int to) { final Object[] es = elementData; if (to > es.length) { throw new ConcurrentModificationException(); } int hashCode = 1; for (int i = from; i < to; i++) { Object e = es[i]; hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode()); } return hashCode; } /** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * {@code i} such that * {@code Objects.equals(o, get(i))} * (if such an element exists). Returns {@code true} if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return {@code true} if this list contained the specified element */ public boolean remove(Object o) { final Object[] es = elementData; final int size = this.size; int i = 0; found: { if (o == null) { for (; i < size; i++) if (es[i] == null) break found; } else { for (; i < size; i++) if (o.equals(es[i])) break found; } return false; } fastRemove(es, i); return true; } /** * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(Object[] es, int i) { modCount++; final int newSize; if ((newSize = size - 1) > i) System.arraycopy(es, i + 1, es, i, newSize - i); es[size = newSize] = null; } /** * Removes all of the elements from this list. The list will * be empty after this call returns. */ public void clear() { modCount++; final Object[] es = elementData; for (int to = size, i = size = 0; i < to; i++) es[i] = null; } /** * Appends all of the elements in the specified collection to the end of * this list, in the order that they are returned by the * specified collection's Iterator. The behavior of this operation is * undefined if the specified collection is modified while the operation * is in progress. (This implies that the behavior of this call is * undefined if the specified collection is this list, and this * list is nonempty.) * * @param c collection containing elements to be added to this list * @return {@code true} if this list changed as a result of the call * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection c) { Object[] a = c.toArray(); modCount++; int numNew = a.length; if (numNew == 0) return false; Object[] elementData; final int s; if (numNew > (elementData = this.elementData).length - (s = size)) elementData = grow(s + numNew); System.arraycopy(a, 0, elementData, s, numNew); size = s + numNew; return true; } /** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts the element * currently at that position (if any) and any subsequent elements to * the right (increases their indices). The new elements will appear * in the list in the order that they are returned by the * specified collection's iterator. * * @param index index at which to insert the first element from the * specified collection * @param c collection containing elements to be added to this list * @return {@code true} if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */ public boolean addAll(int index, Collection c) { rangeCheckForAdd(index); Object[] a = c.toArray(); modCount++; int numNew = a.length; if (numNew == 0) return false; Object[] elementData; final int s; if (numNew > (elementData = this.elementData).length - (s = size)) elementData = grow(s + numNew); int numMoved = s - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size = s + numNew; return true; } /** * Removes from this list all of the elements whose index is between * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. * Shifts any succeeding elements to the left (reduces their index). * This call shortens the list by {@code (toIndex - fromIndex)} elements. * (If {@code toIndex==fromIndex}, this operation has no effect.) * * @throws IndexOutOfBoundsException if {@code fromIndex} or * {@code toIndex} is out of range * ({@code fromIndex < 0 || * toIndex > size() || * toIndex < fromIndex}) */ protected void removeRange(int fromIndex, int toIndex) { if (fromIndex > toIndex) { throw new IndexOutOfBoundsException( outOfBoundsMsg(fromIndex, toIndex)); } modCount++; shiftTailOverGap(elementData, fromIndex, toIndex); } /** Erases the gap from lo to hi, by sliding down following elements. */ private void shiftTailOverGap(Object[] es, int lo, int hi) { System.arraycopy(es, hi, es, lo, size - hi); for (int to = size, i = (size -= hi - lo); i < to; i++) es[i] = null; } /** * A version of rangeCheck used by add and addAll. */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * Constructs an IndexOutOfBoundsException detail message. * Of the many possible refactorings of the error handling code, * this ""outlining"" performs best with both server and client VMs. */ private String outOfBoundsMsg(int index) { return ""Index: ""+index+"", Size: ""+size; } /** * A version used in checking (fromIndex > toIndex) condition */ private static String outOfBoundsMsg(int fromIndex, int toIndex) { return ""From Index: "" + fromIndex + "" > To Index: "" + toIndex; } /** * Removes from this list all of its elements that are contained in the * specified collection. * * @param c collection containing elements to be removed from this list * @return {@code true} if this list changed as a result of the call * @throws ClassCastException if the class of an element of this list * is incompatible with the specified collection * (optional) * @throws NullPointerException if this list contains a null element and the * specified collection does not permit null elements * (optional), * or if the specified collection is null * @see Collection#contains(Object) */ public boolean removeAll(Collection c) { return batchRemove(c, false, 0, size); } /** * Retains only the elements in this list that are contained in the * specified collection. In other words, removes from this list all * of its elements that are not contained in the specified collection. * * @param c collection containing elements to be retained in this list * @return {@code true} if this list changed as a result of the call * @throws ClassCastException if the class of an element of this list * is incompatible with the specified collection * (optional) * @throws NullPointerException if this list contains a null element and the * specified collection does not permit null elements * (optional), * or if the specified collection is null * @see Collection#contains(Object) */ public boolean retainAll(Collection c) { return batchRemove(c, true, 0, size); } boolean batchRemove(Collection c, boolean complement, final int from, final int end) { Objects.requireNonNull(c); final Object[] es = elementData; int r; // Optimize for initial run of survivors for (r = from;; r++) { if (r == end) return false; if (c.contains(es[r]) != complement) break; } int w = r++; try { for (Object e; r < end; r++) if (c.contains(e = es[r]) == complement) es[w++] = e; } catch (Throwable ex) { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. System.arraycopy(es, r, es, w, end - r); w += end - r; throw ex; } finally { modCount += end - w; shiftTailOverGap(es, w, end); } return true; } /** * Saves the state of the {@code ArrayList} instance to a stream * (that is, serializes it). * * @param s the stream * @throws java.io.IOException if an I/O error occurs * @serialData The length of the array backing the {@code ArrayList} * instance is emitted (int), followed by all of its elements * (each an {@code Object}) in the proper order. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioral compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i

The returned list iterator is fail-fast. * * @throws IndexOutOfBoundsException {@inheritDoc} */ public ListIterator listIterator(int index) { rangeCheckForAdd(index); return new ListItr(index); } /** * Returns a list iterator over the elements in this list (in proper * sequence). * *

The returned list iterator is fail-fast. * * @see #listIterator(int) */ public ListIterator listIterator() { return new ListItr(0); } /** * Returns an iterator over the elements in this list in proper sequence. * *

The returned iterator is fail-fast. * * @return an iterator over the elements in this list in proper sequence */ public Iterator iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator { // Android-changed: Add ""limit"" field to detect end of iteration. // The ""limit"" of this iterator. This is the size of the list at the time the // iterator was created. Adding & removing elements will invalidate the iteration // anyway (and cause next() to throw) so saving this value will guarantee that the // value of hasNext() remains stable and won't flap between true and false when elements // are added and removed from the list. protected int limit = ArrayList.this.size; int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; // prevent creating a synthetic constructor Itr() {} public boolean hasNext() { return cursor < limit; } @SuppressWarnings(""unchecked"") public E next() { checkForComodification(); int i = cursor; if (i >= limit) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; limit--; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); final int size = ArrayList.this.size; int i = cursor; if (i < size) { final Object[] es = elementData; if (i >= es.length) throw new ConcurrentModificationException(); for (; i < size && modCount == expectedModCount; i++) action.accept(elementAt(es, i)); // update once at end to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } /** * An optimized version of AbstractList.ListItr */ private class ListItr extends Itr implements ListIterator { ListItr(int index) { super(); cursor = index; } public boolean hasPrevious() { return cursor != 0; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } @SuppressWarnings(""unchecked"") public E previous() { checkForComodification(); int i = cursor - 1; if (i < 0) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i; return (E) elementData[lastRet = i]; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.set(lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; limit++; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } } /** * Returns a view of the portion of this list between the specified * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If * {@code fromIndex} and {@code toIndex} are equal, the returned list is * empty.) The returned list is backed by this list, so non-structural * changes in the returned list are reflected in this list, and vice-versa. * The returned list supports all of the optional list operations. * *

This method eliminates the need for explicit range operations (of * the sort that commonly exist for arrays). Any operation that expects * a list can be used as a range operation by passing a subList view * instead of a whole list. For example, the following idiom * removes a range of elements from a list: *

     *      list.subList(from, to).clear();
     * 
* Similar idioms may be constructed for {@link #indexOf(Object)} and * {@link #lastIndexOf(Object)}, and all of the algorithms in the * {@link Collections} class can be applied to a subList. * *

The semantics of the list returned by this method become undefined if * the backing list (i.e., this list) is structurally modified in * any way other than via the returned list. (Structural modifications are * those that change the size of this list, or otherwise perturb it in such * a fashion that iterations in progress may yield incorrect results.) * * @throws IndexOutOfBoundsException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ public List subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList<>(this, fromIndex, toIndex); } private static class SubList extends AbstractList implements RandomAccess { private final ArrayList root; private final SubList parent; private final int offset; private int size; /** * Constructs a sublist of an arbitrary ArrayList. */ public SubList(ArrayList root, int fromIndex, int toIndex) { this.root = root; this.parent = null; this.offset = fromIndex; this.size = toIndex - fromIndex; this.modCount = root.modCount; } /** * Constructs a sublist of another SubList. */ private SubList(SubList parent, int fromIndex, int toIndex) { this.root = parent.root; this.parent = parent; this.offset = parent.offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = parent.modCount; } public E set(int index, E element) { Objects.checkIndex(index, size); checkForComodification(); E oldValue = root.elementData(offset + index); root.elementData[offset + index] = element; return oldValue; } public E get(int index) { Objects.checkIndex(index, size); checkForComodification(); return root.elementData(offset + index); } public int size() { checkForComodification(); return size; } public void add(int index, E element) { rangeCheckForAdd(index); checkForComodification(); root.add(offset + index, element); updateSizeAndModCount(1); } public E remove(int index) { Objects.checkIndex(index, size); checkForComodification(); E result = root.remove(offset + index); updateSizeAndModCount(-1); return result; } protected void removeRange(int fromIndex, int toIndex) { checkForComodification(); root.removeRange(offset + fromIndex, offset + toIndex); updateSizeAndModCount(fromIndex - toIndex); } public boolean addAll(Collection c) { return addAll(this.size, c); } public boolean addAll(int index, Collection c) { rangeCheckForAdd(index); int cSize = c.size(); if (cSize==0) return false; checkForComodification(); root.addAll(offset + index, c); updateSizeAndModCount(cSize); return true; } public void replaceAll(UnaryOperator operator) { root.replaceAllRange(operator, offset, offset + size); } public boolean removeAll(Collection c) { return batchRemove(c, false); } public boolean retainAll(Collection c) { return batchRemove(c, true); } private boolean batchRemove(Collection c, boolean complement) { checkForComodification(); int oldSize = root.size; boolean modified = root.batchRemove(c, complement, offset, offset + size); if (modified) updateSizeAndModCount(root.size - oldSize); return modified; } public boolean removeIf(Predicate filter) { checkForComodification(); int oldSize = root.size; boolean modified = root.removeIf(filter, offset, offset + size); if (modified) updateSizeAndModCount(root.size - oldSize); return modified; } public Object[] toArray() { checkForComodification(); return Arrays.copyOfRange(root.elementData, offset, offset + size); } @SuppressWarnings(""unchecked"") public T[] toArray(T[] a) { checkForComodification(); if (a.length < size) return (T[]) Arrays.copyOfRange( root.elementData, offset, offset + size, a.getClass()); System.arraycopy(root.elementData, offset, a, 0, size); if (a.length > size) a[size] = null; return a; } public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof List)) { return false; } boolean equal = root.equalsRange((List)o, offset, offset + size); checkForComodification(); return equal; } public int hashCode() { int hash = root.hashCodeRange(offset, offset + size); checkForComodification(); return hash; } public int indexOf(Object o) { int index = root.indexOfRange(o, offset, offset + size); checkForComodification(); return index >= 0 ? index - offset : -1; } public int lastIndexOf(Object o) { int index = root.lastIndexOfRange(o, offset, offset + size); checkForComodification(); return index >= 0 ? index - offset : -1; } public boolean contains(Object o) { return indexOf(o) >= 0; } public Iterator iterator() { return listIterator(); } public ListIterator listIterator(int index) { checkForComodification(); rangeCheckForAdd(index); return new ListIterator() { int cursor = index; int lastRet = -1; int expectedModCount = SubList.this.modCount; public boolean hasNext() { return cursor != SubList.this.size; } @SuppressWarnings(""unchecked"") public E next() { checkForComodification(); int i = cursor; if (i >= SubList.this.size) throw new NoSuchElementException(); Object[] elementData = root.elementData; if (offset + i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[offset + (lastRet = i)]; } public boolean hasPrevious() { return cursor != 0; } @SuppressWarnings(""unchecked"") public E previous() { checkForComodification(); int i = cursor - 1; if (i < 0) throw new NoSuchElementException(); Object[] elementData = root.elementData; if (offset + i >= elementData.length) throw new ConcurrentModificationException(); cursor = i; return (E) elementData[offset + (lastRet = i)]; } public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); final int size = SubList.this.size; int i = cursor; if (i < size) { final Object[] es = root.elementData; if (offset + i >= es.length) throw new ConcurrentModificationException(); for (; i < size && root.modCount == expectedModCount; i++) action.accept(elementAt(es, offset + i)); // update once at end to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { SubList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = SubList.this.modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { root.set(offset + lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; SubList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = SubList.this.modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (root.modCount != expectedModCount) throw new ConcurrentModificationException(); } }; } public List subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList<>(this, fromIndex, toIndex); } private void rangeCheckForAdd(int index) { if (index < 0 || index > this.size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private String outOfBoundsMsg(int index) { return ""Index: ""+index+"", Size: ""+this.size; } private void checkForComodification() { if (root.modCount != modCount) throw new ConcurrentModificationException(); } private void updateSizeAndModCount(int sizeChange) { SubList [MASK] = this; do { [MASK] .size += sizeChange; [MASK] .modCount = root.modCount; [MASK] = [MASK] .parent; } while ( [MASK] != null); } public Spliterator spliterator() { checkForComodification(); // ArrayListSpliterator not used here due to late-binding return new Spliterator() { private int index = offset; // current index, modified on advance/split private int fence = -1; // -1 until used; then one past last index private int expectedModCount; // initialized when fence set private int getFence() { // initialize fence to size on first use int hi; // (a specialized variant appears in method forEach) if ((hi = fence) < 0) { expectedModCount = modCount; hi = fence = offset + size; } return hi; } public ArrayList.ArrayListSpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; // ArrayListSpliterator can be used here as the source is already bound return (lo >= mid) ? null : // divide range in half unless too small root.new ArrayListSpliterator(lo, index = mid, expectedModCount); } public boolean tryAdvance(Consumer action) { Objects.requireNonNull(action); int hi = getFence(), i = index; if (i < hi) { index = i + 1; @SuppressWarnings(""unchecked"") E e = (E)root.elementData[i]; action.accept(e); if (root.modCount != expectedModCount) throw new ConcurrentModificationException(); return true; } return false; } public void forEachRemaining(Consumer action) { Objects.requireNonNull(action); int i, hi, mc; // hoist accesses and checks from loop ArrayList lst = root; Object[] a; if ((a = lst.elementData) != null) { if ((hi = fence) < 0) { mc = modCount; hi = offset + size; } else mc = expectedModCount; if ((i = index) >= 0 && (index = hi) <= a.length) { for (; i < hi; ++i) { @SuppressWarnings(""unchecked"") E e = (E) a[i]; action.accept(e); } if (lst.modCount == mc) return; } } throw new ConcurrentModificationException(); } public long estimateSize() { return getFence() - index; } public int characteristics() { return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; } }; } } /** * @throws NullPointerException {@inheritDoc} */ @Override public void forEach(Consumer action) { Objects.requireNonNull(action); final int expectedModCount = modCount; final Object[] es = elementData; final int size = this.size; for (int i = 0; modCount == expectedModCount && i < size; i++) action.accept(elementAt(es, i)); if (modCount != expectedModCount) throw new ConcurrentModificationException(); } /** * Creates a late-binding * and fail-fast {@link Spliterator} over the elements in this * list. * *

The {@code Spliterator} reports {@link Spliterator#SIZED}, * {@link Spliterator#SUBSIZED}, and {@link Spliterator#ORDERED}. * Overriding implementations should document the reporting of additional * characteristic values. * * @return a {@code Spliterator} over the elements in this list * @since 1.8 */ @Override public Spliterator spliterator() { return new ArrayListSpliterator(0, -1, 0); } /** Index-based split-by-two, lazily initialized Spliterator */ final class ArrayListSpliterator implements Spliterator { /* * If ArrayLists were immutable, or structurally immutable (no * adds, removes, etc), we could implement their spliterators * with Arrays.spliterator. Instead we detect as much * interference during traversal as practical without * sacrificing much performance. We rely primarily on * modCounts. These are not guaranteed to detect concurrency * violations, and are sometimes overly conservative about * within-thread interference, but detect enough problems to * be worthwhile in practice. To carry this out, we (1) lazily * initialize fence and expectedModCount until the latest * point that we need to commit to the state we are checking * against; thus improving precision. (This doesn't apply to * SubLists, that create spliterators with current non-lazy * values). (2) We perform only a single * ConcurrentModificationException check at the end of forEach * (the most performance-sensitive method). When using forEach * (as opposed to iterators), we can normally only detect * interference after actions, not before. Further * CME-triggering checks apply to all other possible * violations of assumptions for example null or too-small * elementData array given its size(), that could only have * occurred due to interference. This allows the inner loop * of forEach to run without any further checks, and * simplifies lambda-resolution. While this does entail a * number of checks, note that in the common case of * list.stream().forEach(a), no checks or other computation * occur anywhere other than inside forEach itself. The other * less-often-used methods cannot take advantage of most of * these streamlinings. */ private int index; // current index, modified on advance/split private int fence; // -1 until used; then one past last index private int expectedModCount; // initialized when fence set /** Creates new spliterator covering the given range. */ ArrayListSpliterator(int origin, int fence, int expectedModCount) { this.index = origin; this.fence = fence; this.expectedModCount = expectedModCount; } private int getFence() { // initialize fence to size on first use int hi; // (a specialized variant appears in method forEach) if ((hi = fence) < 0) { expectedModCount = modCount; hi = fence = size; } return hi; } public ArrayListSpliterator trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; return (lo >= mid) ? null : // divide range in half unless too small new ArrayListSpliterator(lo, index = mid, expectedModCount); } public boolean tryAdvance(Consumer action) { if (action == null) throw new NullPointerException(); int hi = getFence(), i = index; if (i < hi) { index = i + 1; @SuppressWarnings(""unchecked"") E e = (E)elementData[i]; action.accept(e); if (modCount != expectedModCount) throw new ConcurrentModificationException(); return true; } return false; } public void forEachRemaining(Consumer action) { int i, hi, mc; // hoist accesses and checks from loop Object[] a; if (action == null) throw new NullPointerException(); if ((a = elementData) != null) { if ((hi = fence) < 0) { mc = modCount; hi = size; } else mc = expectedModCount; if ((i = index) >= 0 && (index = hi) <= a.length) { for (; i < hi; ++i) { @SuppressWarnings(""unchecked"") E e = (E) a[i]; action.accept(e); } if (modCount == mc) return; } } throw new ConcurrentModificationException(); } public long estimateSize() { return getFence() - index; } public int characteristics() { return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; } } // A tiny bit set implementation private static long[] nBits(int n) { return new long[((n - 1) >> 6) + 1]; } private static void setBit(long[] bits, int i) { bits[i >> 6] |= 1L << i; } private static boolean isClear(long[] bits, int i) { return (bits[i >> 6] & (1L << i)) == 0; } /** * @throws NullPointerException {@inheritDoc} */ @Override public boolean removeIf(Predicate filter) { return removeIf(filter, 0, size); } /** * Removes all elements satisfying the given predicate, from index * i (inclusive) to index end (exclusive). */ boolean removeIf(Predicate filter, int i, final int end) { Objects.requireNonNull(filter); int expectedModCount = modCount; final Object[] es = elementData; // Optimize for initial run of survivors for (; i < end && !filter.test(elementAt(es, i)); i++) ; // Tolerate predicates that reentrantly access the collection for // read (but writers still get CME), so traverse once to find // elements to delete, a second pass to physically expunge. if (i < end) { final int beg = i; final long[] deathRow = nBits(end - beg); deathRow[0] = 1L; // set bit 0 for (i = beg + 1; i < end; i++) if (filter.test(elementAt(es, i))) setBit(deathRow, i - beg); if (modCount != expectedModCount) throw new ConcurrentModificationException(); modCount++; int w = beg; for (i = beg; i < end; i++) if (isClear(deathRow, i - beg)) es[w++] = es[i]; shiftTailOverGap(es, w, end); return true; } else { if (modCount != expectedModCount) throw new ConcurrentModificationException(); return false; } } @Override public void replaceAll(UnaryOperator operator) { replaceAllRange(operator, 0, size); modCount++; } private void replaceAllRange(UnaryOperator operator, int i, int end) { Objects.requireNonNull(operator); final int expectedModCount = modCount; final Object[] es = elementData; for (; modCount == expectedModCount && i < end; i++) es[i] = operator.apply(elementAt(es, i)); if (modCount != expectedModCount) throw new ConcurrentModificationException(); } @Override @SuppressWarnings(""unchecked"") public void sort(Comparator c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) throw new ConcurrentModificationException(); modCount++; } void checkInvariants() { // assert size >= 0; // assert size == elementData.length || elementData[size] == null; } /*-[ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len { if (state->state == 0) { state->mutationsPtr = (unsigned long *) &modCount_; state->itemsPtr = (__unsafe_unretained id *) (void *) elementData_->buffer_; state->state = 1; return size_; } else { return 0; } } ]-*/ } ","slist " "// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.common.options.testing; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.devtools.common.options.Converters; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for the ConverterTesterMap map builder. */ @RunWith(JUnit4.class) public final class ConverterTesterMapTest { @Test public void add_mapsTestedConverterClassToTester() throws Exception { ConverterTester stringTester = new ConverterTester(Converters.StringConverter.class, /*conversionContext=*/ null); ConverterTester intTester = new ConverterTester(Converters.IntegerConverter.class, /*conversionContext=*/ null); ConverterTester doubleTester = new ConverterTester(Converters.DoubleConverter.class, /*conversionContext=*/ null); ConverterTester booleanTester = new ConverterTester(Converters.BooleanConverter.class, /*conversionContext=*/ null); ConverterTesterMap map = new ConverterTesterMap.Builder() .add(stringTester) .add(intTester) .add(doubleTester) .add(booleanTester) .build(); assertThat(map) .containsExactly( Converters.StringConverter.class, stringTester, Converters.IntegerConverter.class, intTester, Converters.DoubleConverter.class, doubleTester, Converters.BooleanConverter.class, booleanTester); } @Test public void addAll_mapsTestedConverterClassesToTester() throws Exception { ConverterTester stringTester = new ConverterTester(Converters.StringConverter.class, /*conversionContext=*/ null); ConverterTester intTester = new ConverterTester(Converters.IntegerConverter.class, /*conversionContext=*/ null); ConverterTester doubleTester = new ConverterTester(Converters.DoubleConverter.class, /*conversionContext=*/ null); ConverterTester booleanTester = new ConverterTester(Converters.BooleanConverter.class, /*conversionContext=*/ null); ConverterTesterMap map = new ConverterTesterMap.Builder() .addAll(ImmutableList.of(stringTester, intTester, doubleTester, booleanTester)) .build(); assertThat(map) .containsExactly( Converters.StringConverter.class, stringTester, Converters.IntegerConverter.class, intTester, Converters.DoubleConverter.class, doubleTester, Converters.BooleanConverter.class, booleanTester); } @Test public void addAll_dumpsConverterTesterMapIntoNewMap() throws Exception { ConverterTester stringTester = new ConverterTester(Converters.StringConverter.class, /*conversionContext=*/ null); ConverterTester intTester = new ConverterTester(Converters.IntegerConverter.class, /*conversionContext=*/ null); ConverterTester doubleTester = new ConverterTester(Converters.DoubleConverter.class, /*conversionContext=*/ null); ConverterTester booleanTester = new ConverterTester(Converters.BooleanConverter.class, /*conversionContext=*/ null); ConverterTesterMap [MASK] = new ConverterTesterMap.Builder() .addAll(ImmutableList.of(stringTester, intTester, doubleTester)) .build(); ConverterTesterMap map = new ConverterTesterMap.Builder().addAll( [MASK] ).add(booleanTester).build(); assertThat(map) .containsExactly( Converters.StringConverter.class, stringTester, Converters.IntegerConverter.class, intTester, Converters.DoubleConverter.class, doubleTester, Converters.BooleanConverter.class, booleanTester); } @Test public void build_forbidsDuplicates() throws Exception { ConverterTesterMap.Builder builder = new ConverterTesterMap.Builder() .add(new ConverterTester(Converters.StringConverter.class, /*conversionContext=*/ null)) .add( new ConverterTester(Converters.IntegerConverter.class, /*conversionContext=*/ null)) .add(new ConverterTester(Converters.DoubleConverter.class, /*conversionContext=*/ null)) .add( new ConverterTester(Converters.BooleanConverter.class, /*conversionContext=*/ null)) .add( new ConverterTester( Converters.BooleanConverter.class, /*conversionContext=*/ null)); IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> builder.build()); assertThat(expected) .hasMessageThat() .contains(Converters.BooleanConverter.class.getSimpleName()); } } ","baseMap " "/* * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.zxing.qrcode.encoder; /** * @author Satoru Takabayashi * @author Daniel Switkin * @author Sean Owen */ final class MaskUtil { // Penalty weights from section 6.8.2.1 private static final int N1 = 3; private static final int N2 = 3; private static final int N3 = 40; private static final int N4 = 10; private MaskUtil() { // do nothing } /** * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and * give penalty to them. Example: 00000 or 11111. */ static int applyMaskPenaltyRule1(ByteMatrix matrix) { return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false); } /** * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block. */ static int applyMaskPenaltyRule2(ByteMatrix matrix) { int penalty = 0; byte[][] array = matrix.getArray(); int width = matrix.getWidth(); int height = matrix.getHeight(); for (int y = 0; y < height - 1; y++) { byte[] arrayY = array[y]; for (int x = 0; x < width - 1; x++) { int value = arrayY[x]; if (value == arrayY[x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) { penalty++; } } } return N2 * penalty; } /** * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4 * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we * find patterns like 000010111010000, we give penalty once. */ static int applyMaskPenaltyRule3(ByteMatrix matrix) { int numPenalties = 0; byte[][] array = matrix.getArray(); int width = matrix.getWidth(); int height = matrix.getHeight(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { byte[] arrayY = array[y]; // We can at least optimize this access if (x + 6 < width && arrayY[x] == 1 && arrayY[x + 1] == 0 && arrayY[x + 2] == 1 && arrayY[x + 3] == 1 && arrayY[x + 4] == 1 && arrayY[x + 5] == 0 && arrayY[x + 6] == 1 && (isWhiteHorizontal(arrayY, x - 4, x) || isWhiteHorizontal(arrayY, x + 7, x + 11))) { numPenalties++; } if (y + 6 < height && array[y][x] == 1 && array[y + 1][x] == 0 && array[y + 2][x] == 1 && array[y + 3][x] == 1 && array[y + 4][x] == 1 && array[y + 5][x] == 0 && array[y + 6][x] == 1 && (isWhiteVertical(array, x, y - 4, y) || isWhiteVertical(array, x, y + 7, y + 11))) { numPenalties++; } } } return numPenalties * N3; } private static boolean isWhiteHorizontal(byte[] rowArray, int from, int to) { if (from < 0 || rowArray.length < to) { return false; } for (int i = from; i < to; i++) { if (rowArray[i] == 1) { return false; } } return true; } private static boolean isWhiteVertical(byte[][] array, int col, int from, int to) { if (from < 0 || array.length < to) { return false; } for (int i = from; i < to; i++) { if (array[i][col] == 1) { return false; } } return true; } /** * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. */ static int applyMaskPenaltyRule4(ByteMatrix matrix) { int numDarkCells = 0; byte[][] array = matrix.getArray(); int width = matrix.getWidth(); int height = matrix.getHeight(); for (int y = 0; y < height; y++) { byte[] arrayY = array[y]; for (int x = 0; x < width; x++) { if (arrayY[x] == 1) { numDarkCells++; } } } int numTotalCells = matrix.getHeight() * matrix.getWidth(); int fivePercentVariances = Math.abs(numDarkCells * 2 - numTotalCells) * 10 / numTotalCells; return fivePercentVariances * N4; } /** * Return the mask bit for ""getMaskPattern"" at ""x"" and ""y"". See 8.8 of JISX0510:2004 for mask * pattern conditions. */ static boolean getDataMaskBit(int maskPattern, int x, int y) { int intermediate; int temp; switch (maskPattern) { case 0: intermediate = (y + x) & 0x1; break; case 1: intermediate = y & 0x1; break; case 2: intermediate = x % 3; break; case 3: intermediate = (y + x) % 3; break; case 4: intermediate = ((y / 2) + (x / 3)) & 0x1; break; case 5: temp = y * x; intermediate = (temp & 0x1) + (temp % 3); break; case 6: temp = y * x; intermediate = ((temp & 0x1) + (temp % 3)) & 0x1; break; case 7: temp = y * x; intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1; break; default: throw new IllegalArgumentException(""Invalid mask pattern: "" + maskPattern); } return intermediate == 0; } /** * Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both * vertical and horizontal orders respectively. */ private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) { int penalty = 0; int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth(); int [MASK] = isHorizontal ? matrix.getWidth() : matrix.getHeight(); byte[][] array = matrix.getArray(); for (int i = 0; i < iLimit; i++) { int numSameBitCells = 0; int prevBit = -1; for (int j = 0; j < [MASK] ; j++) { int bit = isHorizontal ? array[i][j] : array[j][i]; if (bit == prevBit) { numSameBitCells++; } else { if (numSameBitCells >= 5) { penalty += N1 + (numSameBitCells - 5); } numSameBitCells = 1; // Include the cell itself. prevBit = bit; } } if (numSameBitCells >= 5) { penalty += N1 + (numSameBitCells - 5); } } return penalty; } } ","jLimit " "package com.blankj.utilcode.util; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.blankj.utilcode.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import androidx.annotation.CallSuper; import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringDef; import androidx.annotation.StringRes; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; /** *

 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/09/29
 *     desc  : utils about toast
 * 
*/ public final class ToastUtils { @StringDef({MODE.LIGHT, MODE.DARK}) @Retention(RetentionPolicy.SOURCE) public @interface MODE { String LIGHT = ""light""; String DARK = ""dark""; } private static final String TAG_TOAST = ""TAG_TOAST""; private static final int COLOR_DEFAULT = 0xFEFFFFFF; private static final String NULL = ""toast null""; private static final String NOTHING = ""toast nothing""; private static final ToastUtils DEFAULT_MAKER = make(); private static WeakReference sWeakToast; private String mMode; private int mGravity = -1; private int mXOffset = -1; private int mYOffset = -1; private int mBgColor = COLOR_DEFAULT; private int mBgResource = -1; private int mTextColor = COLOR_DEFAULT; private int mTextSize = -1; private boolean isLong = false; private Drawable[] mIcons = new Drawable[4]; private boolean isNotUseSystemToast = false; /** * Make a toast. * * @return the single {@link ToastUtils} instance */ @NonNull public static ToastUtils make() { return new ToastUtils(); } /** * @param mode The mode. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setMode(@MODE String mode) { mMode = mode; return this; } /** * Set the gravity. * * @param gravity The gravity. * @param xOffset X-axis offset, in pixel. * @param yOffset Y-axis offset, in pixel. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setGravity(final int gravity, final int xOffset, final int yOffset) { mGravity = gravity; mXOffset = xOffset; mYOffset = yOffset; return this; } /** * Set the color of background. * * @param backgroundColor The color of background. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setBgColor(@ColorInt final int backgroundColor) { mBgColor = backgroundColor; return this; } /** * Set the resource of background. * * @param bgResource The resource of background. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setBgResource(@DrawableRes final int bgResource) { mBgResource = bgResource; return this; } /** * Set the text color of toast. * * @param msgColor The text color of toast. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setTextColor(@ColorInt final int msgColor) { mTextColor = msgColor; return this; } /** * Set the text size of toast. * * @param textSize The text size of toast. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setTextSize(final int textSize) { mTextSize = textSize; return this; } /** * Set the toast for a long period of time. * * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setDurationIsLong(boolean isLong) { this.isLong = isLong; return this; } /** * Set the left icon of toast. * * @param resId The left icon resource identifier. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setLeftIcon(@DrawableRes int resId) { return setLeftIcon(ContextCompat.getDrawable(Utils.getApp(), resId)); } /** * Set the left icon of toast. * * @param drawable The left icon drawable. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setLeftIcon(@Nullable Drawable drawable) { mIcons[0] = drawable; return this; } /** * Set the top icon of toast. * * @param resId The top icon resource identifier. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setTopIcon(@DrawableRes int resId) { return setTopIcon(ContextCompat.getDrawable(Utils.getApp(), resId)); } /** * Set the top icon of toast. * * @param drawable The top icon drawable. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setTopIcon(@Nullable Drawable drawable) { mIcons[1] = drawable; return this; } /** * Set the right icon of toast. * * @param resId The right icon resource identifier. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setRightIcon(@DrawableRes int resId) { return setRightIcon(ContextCompat.getDrawable(Utils.getApp(), resId)); } /** * Set the right icon of toast. * * @param drawable The right icon drawable. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setRightIcon(@Nullable Drawable drawable) { mIcons[2] = drawable; return this; } /** * Set the left bottom of toast. * * @param resId The bottom icon resource identifier. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setBottomIcon(int resId) { return setBottomIcon(ContextCompat.getDrawable(Utils.getApp(), resId)); } /** * Set the bottom icon of toast. * * @param drawable The bottom icon drawable. * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setBottomIcon(@Nullable Drawable drawable) { mIcons[3] = drawable; return this; } /** * Set not use system toast. * * @return the single {@link ToastUtils} instance */ @NonNull public final ToastUtils setNotUseSystemToast() { isNotUseSystemToast = true; return this; } /** * Return the default {@link ToastUtils} instance. * * @return the default {@link ToastUtils} instance */ @NonNull public static ToastUtils getDefaultMaker() { return DEFAULT_MAKER; } /** * Show the toast for a short period of time. * * @param text The text. */ public final void show(@Nullable final CharSequence text) { show(text, getDuration(), this); } /** * Show the toast for a short period of time. * * @param resId The resource id for text. */ public final void show(@StringRes final int resId) { show(UtilsBridge.getString(resId), getDuration(), this); } /** * Show the toast for a short period of time. * * @param resId The resource id for text. * @param args The args. */ public final void show(@StringRes final int resId, final Object... args) { show(UtilsBridge.getString(resId, args), getDuration(), this); } /** * Show the toast for a short period of time. * * @param format The format. * @param args The args. */ public final void show(@Nullable final String format, final Object... args) { show(UtilsBridge.format(format, args), getDuration(), this); } /** * Show custom toast. */ public final void show(@NonNull final View view) { show(view, getDuration(), this); } private int getDuration() { return isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT; } private View tryApplyUtilsToastView(final CharSequence text) { if (!MODE.DARK.equals(mMode) && !MODE.LIGHT.equals(mMode) && mIcons[0] == null && mIcons[1] == null && mIcons[2] == null && mIcons[3] == null) { return null; } View toastView = UtilsBridge.layoutId2View(R.layout.utils_toast_view); TextView messageTv = toastView.findViewById(android.R.id.message); if (MODE.DARK.equals(mMode)) { GradientDrawable bg = (GradientDrawable) toastView.getBackground().mutate(); bg.setColor(Color.parseColor(""#BB000000"")); messageTv.setTextColor(Color.WHITE); } messageTv.setText(text); if (mIcons[0] != null) { View leftIconView = toastView.findViewById(R.id.utvLeftIconView); ViewCompat.setBackground(leftIconView, mIcons[0]); leftIconView.setVisibility(View.VISIBLE); } if (mIcons[1] != null) { View topIconView = toastView.findViewById(R.id.utvTopIconView); ViewCompat.setBackground(topIconView, mIcons[1]); topIconView.setVisibility(View.VISIBLE); } if (mIcons[2] != null) { View [MASK] = toastView.findViewById(R.id.utvRightIconView); ViewCompat.setBackground( [MASK] , mIcons[2]); [MASK] .setVisibility(View.VISIBLE); } if (mIcons[3] != null) { View bottomIconView = toastView.findViewById(R.id.utvBottomIconView); ViewCompat.setBackground(bottomIconView, mIcons[3]); bottomIconView.setVisibility(View.VISIBLE); } return toastView; } /** * Show the toast for a short period of time. * * @param text The text. */ public static void showShort(@Nullable final CharSequence text) { show(text, Toast.LENGTH_SHORT, DEFAULT_MAKER); } /** * Show the toast for a short period of time. * * @param resId The resource id for text. */ public static void showShort(@StringRes final int resId) { show(UtilsBridge.getString(resId), Toast.LENGTH_SHORT, DEFAULT_MAKER); } /** * Show the toast for a short period of time. * * @param resId The resource id for text. * @param args The args. */ public static void showShort(@StringRes final int resId, final Object... args) { show(UtilsBridge.getString(resId, args), Toast.LENGTH_SHORT, DEFAULT_MAKER); } /** * Show the toast for a short period of time. * * @param format The format. * @param args The args. */ public static void showShort(@Nullable final String format, final Object... args) { show(UtilsBridge.format(format, args), Toast.LENGTH_SHORT, DEFAULT_MAKER); } /** * Show the toast for a long period of time. * * @param text The text. */ public static void showLong(@Nullable final CharSequence text) { show(text, Toast.LENGTH_LONG, DEFAULT_MAKER); } /** * Show the toast for a long period of time. * * @param resId The resource id for text. */ public static void showLong(@StringRes final int resId) { show(UtilsBridge.getString(resId), Toast.LENGTH_LONG, DEFAULT_MAKER); } /** * Show the toast for a long period of time. * * @param resId The resource id for text. * @param args The args. */ public static void showLong(@StringRes final int resId, final Object... args) { show(UtilsBridge.getString(resId, args), Toast.LENGTH_LONG, DEFAULT_MAKER); } /** * Show the toast for a long period of time. * * @param format The format. * @param args The args. */ public static void showLong(@Nullable final String format, final Object... args) { show(UtilsBridge.format(format, args), Toast.LENGTH_LONG, DEFAULT_MAKER); } /** * Cancel the toast. */ public static void cancel() { UtilsBridge.runOnUiThread(new Runnable() { @Override public void run() { if (sWeakToast != null) { final IToast iToast = ToastUtils.sWeakToast.get(); if (iToast != null) { iToast.cancel(); } sWeakToast = null; } } }); } private static void show(@Nullable final CharSequence text, final int duration, final ToastUtils utils) { show(null, getToastFriendlyText(text), duration, utils); } private static void show(@NonNull final View view, final int duration, final ToastUtils utils) { show(view, null, duration, utils); } private static void show(@Nullable final View view, @Nullable final CharSequence text, final int duration, @NonNull final ToastUtils utils) { UtilsBridge.runOnUiThread(new Runnable() { @Override public void run() { cancel(); IToast iToast = newToast(utils); ToastUtils.sWeakToast = new WeakReference<>(iToast); if (view != null) { iToast.setToastView(view); } else { iToast.setToastView(text); } iToast.show(duration); } }); } private static CharSequence getToastFriendlyText(CharSequence src) { CharSequence text = src; if (text == null) { text = NULL; } else if (text.length() == 0) { text = NOTHING; } return text; } private static IToast newToast(ToastUtils toastUtils) { if (!toastUtils.isNotUseSystemToast) { if (NotificationManagerCompat.from(Utils.getApp()).areNotificationsEnabled()) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return new SystemToast(toastUtils); } if (!UtilsBridge.isGrantedDrawOverlays()) { return new SystemToast(toastUtils); } } } // not use system or notification disable if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_TOAST); } else if (UtilsBridge.isGrantedDrawOverlays()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } else { return new WindowManagerToast(toastUtils, WindowManager.LayoutParams.TYPE_PHONE); } } return new ActivityToast(toastUtils); } static final class SystemToast extends AbsToast { SystemToast(ToastUtils toastUtils) { super(toastUtils); if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { try { //noinspection JavaReflectionMemberAccess Field mTNField = Toast.class.getDeclaredField(""mTN""); mTNField.setAccessible(true); Object mTN = mTNField.get(mToast); Field mTNmHandlerField = mTNField.getType().getDeclaredField(""mHandler""); mTNmHandlerField.setAccessible(true); Handler tnHandler = (Handler) mTNmHandlerField.get(mTN); mTNmHandlerField.set(mTN, new SafeHandler(tnHandler)); } catch (Exception ignored) {/**/} } } @Override public void show(int duration) { if (mToast == null) return; mToast.setDuration(duration); mToast.show(); } static class SafeHandler extends Handler { private Handler impl; SafeHandler(Handler impl) { this.impl = impl; } @Override public void handleMessage(@NonNull Message msg) { impl.handleMessage(msg); } @Override public void dispatchMessage(@NonNull Message msg) { try { impl.dispatchMessage(msg); } catch (Exception e) { e.printStackTrace(); } } } } static final class WindowManagerToast extends AbsToast { private WindowManager mWM; private WindowManager.LayoutParams mParams; WindowManagerToast(ToastUtils toastUtils, int type) { super(toastUtils); mParams = new WindowManager.LayoutParams(); mWM = (WindowManager) Utils.getApp().getSystemService(Context.WINDOW_SERVICE); mParams.type = type; } WindowManagerToast(ToastUtils toastUtils, WindowManager wm, int type) { super(toastUtils); mParams = new WindowManager.LayoutParams(); mWM = wm; mParams.type = type; } @Override public void show(final int duration) { if (mToast == null) return; mParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mParams.format = PixelFormat.TRANSLUCENT; mParams.windowAnimations = android.R.style.Animation_Toast; mParams.setTitle(""ToastWithoutNotification""); mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mParams.packageName = Utils.getApp().getPackageName(); mParams.gravity = mToast.getGravity(); if ((mParams.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((mParams.gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.x = mToast.getXOffset(); mParams.y = mToast.getYOffset(); mParams.horizontalMargin = mToast.getHorizontalMargin(); mParams.verticalMargin = mToast.getVerticalMargin(); try { if (mWM != null) { mWM.addView(mToastView, mParams); } } catch (Exception ignored) {/**/} UtilsBridge.runOnUiThreadDelayed(new Runnable() { @Override public void run() { cancel(); } }, duration == Toast.LENGTH_SHORT ? 2000 : 3500); } @Override public void cancel() { try { if (mWM != null) { mWM.removeViewImmediate(mToastView); mWM = null; } } catch (Exception ignored) {/**/} super.cancel(); } } static final class ActivityToast extends AbsToast { private static int sShowingIndex = 0; private Utils.ActivityLifecycleCallbacks mActivityLifecycleCallbacks; private IToast iToast; ActivityToast(ToastUtils toastUtils) { super(toastUtils); } @Override public void show(int duration) { if (mToast == null) return; if (!UtilsBridge.isAppForeground()) { // try to use system toast iToast = showSystemToast(duration); return; } boolean hasAliveActivity = false; for (final Activity activity : UtilsBridge.getActivityList()) { if (!UtilsBridge.isActivityAlive(activity)) { continue; } if (!hasAliveActivity) { hasAliveActivity = true; iToast = showWithActivityWindow(activity, duration); } else { showWithActivityView(activity, sShowingIndex, true); } } if (hasAliveActivity) { registerLifecycleCallback(); UtilsBridge.runOnUiThreadDelayed(new Runnable() { @Override public void run() { cancel(); } }, duration == Toast.LENGTH_SHORT ? 2000 : 3500); ++sShowingIndex; } else { // try to use system toast iToast = showSystemToast(duration); } } @Override public void cancel() { if (isShowing()) { unregisterLifecycleCallback(); for (Activity activity : UtilsBridge.getActivityList()) { if (!UtilsBridge.isActivityAlive(activity)) { continue; } final Window window = activity.getWindow(); if (window != null) { ViewGroup decorView = (ViewGroup) window.getDecorView(); View toastView = decorView.findViewWithTag(TAG_TOAST + (sShowingIndex - 1)); if (toastView != null) { try { decorView.removeView(toastView); } catch (Exception ignored) {/**/} } } } } if (iToast != null) { iToast.cancel(); iToast = null; } super.cancel(); } private IToast showSystemToast(int duration) { SystemToast systemToast = new SystemToast(mToastUtils); systemToast.mToast = mToast; systemToast.show(duration); return systemToast; } private IToast showWithActivityWindow(Activity activity, int duration) { WindowManagerToast wmToast = new WindowManagerToast(mToastUtils, activity.getWindowManager(), WindowManager.LayoutParams.LAST_APPLICATION_WINDOW); wmToast.mToastView = getToastViewSnapshot(-1); wmToast.mToast = mToast; wmToast.show(duration); return wmToast; } private void showWithActivityView(final Activity activity, final int index, boolean useAnim) { final Window window = activity.getWindow(); if (window != null) { final ViewGroup decorView = (ViewGroup) window.getDecorView(); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); lp.gravity = mToast.getGravity(); lp.bottomMargin = mToast.getYOffset() + UtilsBridge.getNavBarHeight(); lp.topMargin = mToast.getYOffset() + UtilsBridge.getStatusBarHeight(); lp.leftMargin = mToast.getXOffset(); View toastViewSnapshot = getToastViewSnapshot(index); if (useAnim) { toastViewSnapshot.setAlpha(0); toastViewSnapshot.animate().alpha(1).setDuration(200).start(); } decorView.addView(toastViewSnapshot, lp); } } private void registerLifecycleCallback() { final int index = sShowingIndex; mActivityLifecycleCallbacks = new Utils.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(@NonNull Activity activity) { if (isShowing()) { showWithActivityView(activity, index, false); } } }; UtilsBridge.addActivityLifecycleCallbacks(mActivityLifecycleCallbacks); } private void unregisterLifecycleCallback() { UtilsBridge.removeActivityLifecycleCallbacks(mActivityLifecycleCallbacks); mActivityLifecycleCallbacks = null; } private boolean isShowing() { return mActivityLifecycleCallbacks != null; } } static abstract class AbsToast implements IToast { protected Toast mToast; protected ToastUtils mToastUtils; protected View mToastView; AbsToast(ToastUtils toastUtils) { mToast = new Toast(Utils.getApp()); mToastUtils = toastUtils; if (mToastUtils.mGravity != -1 || mToastUtils.mXOffset != -1 || mToastUtils.mYOffset != -1) { mToast.setGravity(mToastUtils.mGravity, mToastUtils.mXOffset, mToastUtils.mYOffset); } } @Override public void setToastView(View view) { mToastView = view; mToast.setView(mToastView); } @Override public void setToastView(CharSequence text) { View utilsToastView = mToastUtils.tryApplyUtilsToastView(text); if (utilsToastView != null) { setToastView(utilsToastView); processRtlIfNeed(); return; } mToastView = mToast.getView(); if (mToastView == null || mToastView.findViewById(android.R.id.message) == null) { setToastView(UtilsBridge.layoutId2View(R.layout.utils_toast_view)); } TextView messageTv = mToastView.findViewById(android.R.id.message); messageTv.setText(text); if (mToastUtils.mTextColor != COLOR_DEFAULT) { messageTv.setTextColor(mToastUtils.mTextColor); } if (mToastUtils.mTextSize != -1) { messageTv.setTextSize(mToastUtils.mTextSize); } setBg(messageTv); processRtlIfNeed(); } private void processRtlIfNeed() { if (UtilsBridge.isLayoutRtl()) { setToastView(getToastViewSnapshot(-1)); } } private void setBg(final TextView msgTv) { if (mToastUtils.mBgResource != -1) { mToastView.setBackgroundResource(mToastUtils.mBgResource); msgTv.setBackgroundColor(Color.TRANSPARENT); } else if (mToastUtils.mBgColor != COLOR_DEFAULT) { Drawable toastBg = mToastView.getBackground(); Drawable msgBg = msgTv.getBackground(); if (toastBg != null && msgBg != null) { toastBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN)); msgTv.setBackgroundColor(Color.TRANSPARENT); } else if (toastBg != null) { toastBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN)); } else if (msgBg != null) { msgBg.mutate().setColorFilter(new PorterDuffColorFilter(mToastUtils.mBgColor, PorterDuff.Mode.SRC_IN)); } else { mToastView.setBackgroundColor(mToastUtils.mBgColor); } } } @Override @CallSuper public void cancel() { if (mToast != null) { mToast.cancel(); } mToast = null; mToastView = null; } View getToastViewSnapshot(final int index) { Bitmap bitmap = UtilsBridge.view2Bitmap(mToastView); ImageView toastIv = new ImageView(Utils.getApp()); toastIv.setTag(TAG_TOAST + index); toastIv.setImageBitmap(bitmap); return toastIv; } } interface IToast { void setToastView(View view); void setToastView(CharSequence text); void show(int duration); void cancel(); } public static final class UtilsMaxWidthRelativeLayout extends RelativeLayout { private static final int SPACING = UtilsBridge.dp2px(80); public UtilsMaxWidthRelativeLayout(Context context) { super(context); } public UtilsMaxWidthRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } public UtilsMaxWidthRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMaxSpec = MeasureSpec.makeMeasureSpec(UtilsBridge.getAppScreenWidth() - SPACING, MeasureSpec.AT_MOST); super.onMeasure(widthMaxSpec, heightMeasureSpec); } } }","rightIconView " "/* * Copyright (c) 2007 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.progress; import org.mockito.internal.verification.Times; import org.mockito.internal.verification.VerificationModeFactory; public class VerificationModeBuilder { private Integer [MASK] = 1; public Times inOrder() { return VerificationModeFactory. [MASK] ( [MASK] ); } public VerificationModeBuilder [MASK] (int [MASK] ) { this. [MASK] = [MASK] ; return this; } } ","times " "/* * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.devtools.cyclefinder; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.io.Files; import com.google.common.io.Resources; import com.google.devtools.j2objc.util.ErrorUtil; import com.google.devtools.j2objc.util.ExternalAnnotations; import com.google.devtools.j2objc.util.SourceVersion; import com.google.devtools.j2objc.util.Version; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; class Options { private static final String XBOOTCLASSPATH = ""-Xbootclasspath:""; private static String usageMessage; private static String helpMessage; static { // Load string resources. URL propertiesUrl = Resources.getResource(CycleFinder.class, ""CycleFinder.properties""); Properties properties = new Properties(); try { properties.load(propertiesUrl.openStream()); } catch (IOException e) { System.err.println(""unable to access tool properties: "" + e); System.exit(1); } usageMessage = properties.getProperty(""usage-message""); Preconditions.checkNotNull(usageMessage); helpMessage = properties.getProperty(""help-message""); Preconditions.checkNotNull(helpMessage); } private String sourcepath; private String classpath; private String bootclasspath; private final List suppressListFiles = Lists.newArrayList(); private final List restrictToListFiles = Lists.newArrayList(); private List sourceFiles = Lists.newArrayList(); private String fileEncoding = System.getProperty(""file.encoding"", ""UTF-8""); private boolean printReferenceGraph = false; private SourceVersion sourceVersion = null; private final ExternalAnnotations externalAnnotations = new ExternalAnnotations(); // Flags that are directly forwarded to the javac parser. private static final ImmutableSet PLATFORM_MODULE_SYSTEM_OPTIONS = ImmutableSet.of(""--patch-module"", ""--system"", ""--add-reads""); private final List platformModuleSystemOptions = new ArrayList<>(); public List getSourceFiles() { return sourceFiles; } public void setSourceFiles(List files) { this.sourceFiles = files; } public String getSourcepath() { return sourcepath; } public String getClasspath() { return classpath; } public void setClasspath(String classpath) { this.classpath = classpath; } public String getBootclasspath() { return bootclasspath != null ? bootclasspath : System.getProperty(""sun.boot.class.path""); } public List getSuppressListFiles() { return suppressListFiles; } public void addSuppressListFile(String fileName) { suppressListFiles.add(fileName); } public List getRestrictToFiles() { return restrictToListFiles; } public void addRestrictToFile(String fileName) { restrictToListFiles.add(fileName); } private void addManifest(String manifestFile) throws IOException { try (BufferedReader in = Files.newReader(new File(manifestFile), Charset.forName(fileEncoding))) { for (String line = in.readLine(); line != null; line = in.readLine()) { if (!Strings.isNullOrEmpty(line)) { sourceFiles.add(line.trim()); } } } } public String fileEncoding() { return fileEncoding; } public SourceVersion sourceVersion() { if (sourceVersion == null) { sourceVersion = SourceVersion.defaultVersion(); } return sourceVersion; } @VisibleForTesting void setSourceVersion(SourceVersion sv) { sourceVersion = sv; } public boolean printReferenceGraph() { return printReferenceGraph; } @VisibleForTesting public void setPrintReferenceGraph() { printReferenceGraph = true; } public ExternalAnnotations externalAnnotations() { return externalAnnotations; } @VisibleForTesting public void addExternalAnnotationFile(String file) throws IOException { externalAnnotations.addExternalAnnotationFile(file); } public void addPlatformModuleSystemOptions(String... flags) { Collections.addAll(platformModuleSystemOptions, flags); } public List getPlatformModuleSystemOptions() { return platformModuleSystemOptions; } public static void usage(String invalidUseMsg) { System.err.println(""cycle_finder: "" + invalidUseMsg); System.err.println(usageMessage); System.exit(1); } public static void help(boolean errorExit) { System.err.println(helpMessage); // javac exits with 2, but any non-zero value works. System.exit(errorExit ? 2 : 0); } public static void version() { System.err.println(""cycle_finder "" + Version.jarVersion(Options.class)); System.exit(0); } public static Options parse(String[] args) throws IOException { Options options = new Options(); int nArg = 0; while (nArg < args.length) { String arg = args[nArg]; if (arg.equals(""-sourcepath"")) { if (++nArg == args.length) { usage(""-sourcepath requires an argument""); } options.sourcepath = args[nArg]; } else if (arg.equals(""-classpath"")) { if (++nArg == args.length) { usage(""-classpath requires an argument""); } options.classpath = args[nArg]; } else if (arg.equals(""--suppress-list"") // Deprecated flag names. || arg.equals(""--whitelist"") || arg.equals(""-w"")) { if (++nArg == args.length) { usage(""--suppress-list requires an argument""); } options.suppressListFiles.add(args[nArg]); } else if (arg.equals(""--restrict-to"") // Deprecated flag name. || arg.equals(""--blacklist"")) { if (++nArg == args.length) { usage(""--restrict-to requires an argument""); } options.restrictToListFiles.add(args[nArg]); } else if (arg.equals(""--sourcefilelist"") || arg.equals(""-s"")) { if (++nArg == args.length) { usage(""--sourcefilelist requires an argument""); } options.addManifest(args[nArg]); } else if (arg.startsWith(XBOOTCLASSPATH)) { options.bootclasspath = arg.substring(XBOOTCLASSPATH.length()); } else if (arg.equals(""-encoding"")) { if (++nArg == args.length) { usage(""-encoding requires an argument""); } options.fileEncoding = args[nArg]; } else if (arg.equals(""-source"")) { if (++nArg == args.length) { usage(""-source requires an argument""); } try { options.sourceVersion = SourceVersion.parse(args[nArg]); SourceVersion [MASK] = SourceVersion.getMaxSupportedVersion(); if (options.sourceVersion.version() > [MASK] .version()) { ErrorUtil.warning(""Java "" + options.sourceVersion.version() + "" source version is not "" + ""supported, using Java "" + [MASK] .version() + "".""); options.sourceVersion = [MASK] ; } } catch (IllegalArgumentException e) { usage(""invalid source release: "" + args[nArg]); } } else if (arg.equals(""--print-reference-graph"")) { options.printReferenceGraph = true; } else if (arg.equals(""-external-annotation-file"")) { if (++nArg == args.length) { usage(arg + "" requires an argument""); } options.addExternalAnnotationFile(args[nArg]); } else if (PLATFORM_MODULE_SYSTEM_OPTIONS.contains(arg)) { String option = arg; if (++nArg == args.length) { usage(option + "" requires an argument""); } options.addPlatformModuleSystemOptions(option, args[nArg]); } else if (arg.equals(""-version"")) { version(); } else if (arg.startsWith(""-h"") || arg.equals(""--help"")) { help(false); } else if (arg.startsWith(""-"")) { usage(""invalid flag: "" + arg); } else { break; } ++nArg; } while (nArg < args.length) { options.sourceFiles.add(args[nArg++]); } if (options.sourceFiles.isEmpty()) { usage(""no source files""); } return options; } } ","maxVersion " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.backends.gwt; import java.util.HashMap; import java.util.Map; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; public class GwtPreferences implements Preferences { final String prefix; ObjectMap values = new ObjectMap(); GwtPreferences (String prefix) { this.prefix = prefix + "":""; int prefixLength = this.prefix.length(); if (GwtFiles.LocalStorage != null) { try { for (int i = 0; i < GwtFiles.LocalStorage.getLength(); i++) { String key = GwtFiles.LocalStorage.key(i); if (key.startsWith(prefix)) { String value = GwtFiles.LocalStorage.getItem(key); values.put(key.substring(prefixLength, key.length() - 1), toObject(key, value)); } } } catch (Exception e) { values.clear(); } } } private Object toObject (String key, String value) { if (key.endsWith(""b"")) return Boolean.parseBoolean(value); if (key.endsWith(""i"")) return Integer.parseInt(value); if (key.endsWith(""l"")) return Long.parseLong(value); if (key.endsWith(""f"")) return Float.parseFloat(value); return value; } private String toStorageKey (String key, Object value) { if (value instanceof Boolean) return prefix + key + ""b""; if (value instanceof Integer) return prefix + key + ""i""; if (value instanceof Long) return prefix + key + ""l""; if (value instanceof Float) return prefix + key + ""f""; return prefix + key + ""s""; } @Override public void flush () { if (GwtFiles.LocalStorage != null) { try { // remove all old values for (int i = 0; i < GwtFiles.LocalStorage.getLength(); i++) { String key = GwtFiles.LocalStorage.key(i); if (key.startsWith(prefix)) GwtFiles.LocalStorage.removeItem(key); } // push new values to LocalStorage for (String key : values.keys()) { String storageKey = toStorageKey(key, values.get(key)); String storageValue = """" + values.get(key).toString(); GwtFiles.LocalStorage.setItem(storageKey, storageValue); } } catch (Exception e) { throw new GdxRuntimeException(""Couldn't flush preferences"", e); } } } @Override public Preferences putBoolean (String key, boolean val) { values.put(key, val); return this; } @Override public Preferences putInteger (String key, int val) { values.put(key, val); return this; } @Override public Preferences putLong (String key, long val) { values.put(key, val); return this; } @Override public Preferences putFloat (String key, float val) { values.put(key, val); return this; } @Override public Preferences putString (String key, String val) { values.put(key, val); return this; } @Override public Preferences put (Map vals) { for (String key : vals.keySet()) { values.put(key, vals.get(key)); } return this; } @Override public boolean getBoolean (String key) { Boolean v = (Boolean)values.get(key); return v == null ? false : v; } @Override public int getInteger (String key) { Integer v = (Integer)values.get(key); return v == null ? 0 : v; } @Override public long getLong (String key) { Long v = (Long)values.get(key); return v == null ? 0 : v; } @Override public float getFloat (String key) { Float v = (Float)values.get(key); return v == null ? 0 : v; } @Override public String getString (String key) { String v = (String)values.get(key); return v == null ? """" : v; } @Override public boolean getBoolean (String key, boolean [MASK] ) { Boolean res = (Boolean)values.get(key); return res == null ? [MASK] : res; } @Override public int getInteger (String key, int [MASK] ) { Integer res = (Integer)values.get(key); return res == null ? [MASK] : res; } @Override public long getLong (String key, long [MASK] ) { Long res = (Long)values.get(key); return res == null ? [MASK] : res; } @Override public float getFloat (String key, float [MASK] ) { Float res = (Float)values.get(key); return res == null ? [MASK] : res; } @Override public String getString (String key, String [MASK] ) { String res = (String)values.get(key); return res == null ? [MASK] : res; } @Override public Map get () { HashMap map = new HashMap(); for (String key : values.keys()) { map.put(key, values.get(key)); } return map; } @Override public boolean contains (String key) { return values.containsKey(key); } @Override public void clear () { values.clear(); } @Override public void remove (String key) { values.remove(key); } } ","defValue " "/* * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the ""Classpath"" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.x509; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import sun.security.util.*; /** * This class implements the URIName as required by the GeneralNames * ASN.1 object. *

* [RFC3280] When the subjectAltName extension contains a URI, the name MUST be * stored in the uniformResourceIdentifier (an IA5String). The name MUST * be a non-relative URL, and MUST follow the URL syntax and encoding * rules specified in [RFC 1738]. The name must include both a scheme * (e.g., ""http"" or ""ftp"") and a scheme-specific-part. The scheme- * specific-part must include a fully qualified domain name or IP * address as the host. *

* As specified in [RFC 1738], the scheme name is not case-sensitive * (e.g., ""http"" is equivalent to ""HTTP""). The host part is also not * case-sensitive, but [MASK] components of the scheme-specific-part may * be case-sensitive. When comparing URIs, conforming implementations * MUST compare the scheme and host without regard to case, but assume * the remainder of the scheme-specific-part is case sensitive. *

* [RFC1738] In general, URLs are written as follows: *

 * :
 * 
* A URL contains the name of the scheme being used () followed * by a colon and then a string (the ) whose * interpretation depends on the scheme. *

* While the syntax for the rest of the URL may vary depending on the * particular scheme selected, URL schemes that involve the direct use * of an IP-based protocol to a specified host on the Internet use a * common syntax for the scheme-specific data: *

 * //:@:/
 * 
* [RFC2732] specifies that an IPv6 address contained inside a URL * must be enclosed in square brackets (to allow distinguishing the * colons that separate IPv6 components from the colons that separate * scheme-specific data. *

* @author Amit Kapoor * @author Hemma Prafullchandra * @author Sean Mullan * @author Steve Hanna * @see GeneralName * @see GeneralNames * @see GeneralNameInterface */ public class URIName implements GeneralNameInterface { // private attributes private URI uri; private String host; private DNSName hostDNS; private IPAddressName hostIP; /** * Create the URIName object from the passed encoded Der value. * * @param derValue the encoded DER URIName. * @exception IOException on error. */ public URIName(DerValue derValue) throws IOException { this(derValue.getIA5String()); } /** * Create the URIName object with the specified name. * * @param name the URIName. * @throws IOException if name is not a proper URIName */ public URIName(String name) throws IOException { try { uri = new URI(name); } catch (URISyntaxException use) { throw new IOException(""invalid URI name:"" + name, use); } if (uri.getScheme() == null) { throw new IOException(""URI name must include scheme:"" + name); } host = uri.getHost(); // RFC 3280 says that the host should be non-null, but we allow it to // be null because some widely deployed certificates contain CDP // extensions with URIs that have no hostname (see bugs 4802236 and // 5107944). if (host != null) { if (host.charAt(0) == '[') { // Verify host is a valid IPv6 address name String ipV6Host = host.substring(1, host.length()-1); try { hostIP = new IPAddressName(ipV6Host); } catch (IOException ioe) { throw new IOException(""invalid URI name (host "" + ""portion is not a valid IPv6 address):"" + name); } } else { try { hostDNS = new DNSName(host); } catch (IOException ioe) { // Not a valid DNS Name; see if it is a valid IPv4 // IPAddressName try { hostIP = new IPAddressName(host); } catch (Exception ioe2) { throw new IOException(""invalid URI name (host "" + ""portion is not a valid DNS name, IPv4 address,"" + "" or IPv6 address):"" + name); } } } } } /** * Create the URIName object with the specified name constraint. URI * name constraints syntax is different than SubjectAltNames, etc. See * 4.2.1.11 of RFC 3280. * * @param value the URI name constraint * @throws IOException if name is not a proper URI name constraint */ public static URIName nameConstraint(DerValue value) throws IOException { URI uri; String name = value.getIA5String(); try { uri = new URI(name); } catch (URISyntaxException use) { throw new IOException(""invalid URI name constraint:"" + name, use); } if (uri.getScheme() == null) { String host = uri.getSchemeSpecificPart(); try { DNSName hostDNS; if (host.charAt(0) == '.') { hostDNS = new DNSName(host.substring(1)); } else { hostDNS = new DNSName(host); } return new URIName(uri, host, hostDNS); } catch (IOException ioe) { throw new IOException(""invalid URI name constraint:"" + name, ioe); } } else { throw new IOException(""invalid URI name constraint (should not "" + ""include scheme):"" + name); } } URIName(URI uri, String host, DNSName hostDNS) { this.uri = uri; this.host = host; this.hostDNS = hostDNS; } /** * Return the type of the GeneralName. */ public int getType() { return GeneralNameInterface.NAME_URI; } /** * Encode the URI name into the DerOutputStream. * * @param out the DER stream to encode the URIName to. * @exception IOException on encoding errors. */ public void encode(DerOutputStream out) throws IOException { out.putIA5String(uri.toASCIIString()); } /** * Convert the name into user readable string. */ public String toString() { return ""URIName: "" + uri.toString(); } /** * Compares this name with an [MASK] , for equality. * * @return true iff the names are equivalent according to RFC2459. */ public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof URIName)) { return false; } URIName [MASK] = (URIName) obj; return uri.equals( [MASK] .getURI()); } /** * Returns the URIName as a java.net.URI object */ public URI getURI() { return uri; } /** * Returns this URI name. */ public String getName() { return uri.toString(); } /** * Return the scheme name portion of a URIName * * @returns scheme portion of full name */ public String getScheme() { return uri.getScheme(); } /** * Return the host name or IP address portion of the URIName * * @returns host name or IP address portion of full name */ public String getHost() { return host; } /** * Return the host object type; if host name is a * DNSName, then this host object does not include any * initial ""."" on the name. * * @returns host name as DNSName or IPAddressName */ public Object getHostObject() { if (hostIP != null) { return hostIP; } else { return hostDNS; } } /** * Returns the hash code value for this object. * * @return a hash code value for this object. */ public int hashCode() { return uri.hashCode(); } /** * Return type of constraint inputName places on this name:

    *
  • NAME_DIFF_TYPE = -1: input name is different type from name * (i.e. does not constrain). *
  • NAME_MATCH = 0: input name matches name. *
  • NAME_NARROWS = 1: input name narrows name (is lower in the naming * subtree) *
  • NAME_WIDENS = 2: input name widens name (is higher in the naming * subtree) *
  • NAME_SAME_TYPE = 3: input name does not match or narrow name, but * is same type. *
. * These results are used in checking NameConstraints during * certification path verification. *

* RFC3280: For URIs, the constraint applies to the host part of the name. * The constraint may specify a host or a domain. Examples would be * ""foo.bar.com""; and "".xyz.com"". When the the constraint begins with * a period, it may be expanded with one or more subdomains. That is, * the constraint "".xyz.com"" is satisfied by both abc.xyz.com and * abc.def.xyz.com. However, the constraint "".xyz.com"" is not satisfied * by ""xyz.com"". When the constraint does not begin with a period, it * specifies a host. *

* @param inputName to be checked for being constrained * @returns constraint type above * @throws UnsupportedOperationException if name is not exact match, but * narrowing and widening are not supported for this name type. */ public int constrains(GeneralNameInterface inputName) throws UnsupportedOperationException { int constraintType; if (inputName == null) { constraintType = NAME_DIFF_TYPE; } else if (inputName.getType() != NAME_URI) { constraintType = NAME_DIFF_TYPE; } else { // Assuming from here on that one or both of these is // actually a URI name constraint (not a URI), so we // only need to compare the host portion of the name String [MASK] Host = ((URIName)inputName).getHost(); // Quick check for equality if ( [MASK] Host.equalsIgnoreCase(host)) { constraintType = NAME_MATCH; } else { Object [MASK] HostObject = ((URIName)inputName).getHostObject(); if ((hostDNS == null) || !( [MASK] HostObject instanceof DNSName)) { // If one (or both) is an IP address, only same type constraintType = NAME_SAME_TYPE; } else { // Both host portions are DNS names. Are they domains? boolean thisDomain = (host.charAt(0) == '.'); boolean [MASK] Domain = ( [MASK] Host.charAt(0) == '.'); DNSName [MASK] DNS = (DNSName) [MASK] HostObject; // Run DNSName.constrains. constraintType = hostDNS.constrains( [MASK] DNS); // If neither one is a domain, then they can't // widen or narrow. That's just SAME_TYPE. if ((!thisDomain && ! [MASK] Domain) && ((constraintType == NAME_WIDENS) || (constraintType == NAME_NARROWS))) { constraintType = NAME_SAME_TYPE; } // If one is a domain and the [MASK] isn't, // then they can't match. The one that's a // domain doesn't include the one that's // not a domain. if ((thisDomain != [MASK] Domain) && (constraintType == NAME_MATCH)) { if (thisDomain) { constraintType = NAME_WIDENS; } else { constraintType = NAME_NARROWS; } } } } } return constraintType; } /** * Return subtree depth of this name for purposes of determining * NameConstraints minimum and maximum bounds and for calculating * path lengths in name subtrees. * * @returns distance of name from root * @throws UnsupportedOperationException if not supported for this name type */ public int subtreeDepth() throws UnsupportedOperationException { DNSName dnsName = null; try { dnsName = new DNSName(host); } catch (IOException ioe) { throw new UnsupportedOperationException(ioe.getMessage()); } return dnsName.subtreeDepth(); } } ","other " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard; import proguard.classfile.*; import proguard.classfile.util.ClassUtil; import proguard.util.*; import java.io.*; import java.net.*; import java.util.*; /** * This class parses ProGuard configurations. Configurations can be read from an * array of arguments or from a configuration file or URL. External references * in file names ('<...>') can be resolved against a given set of properties. * * @author Eric Lafortune */ public class ConfigurationParser { private final WordReader reader; private final Properties properties; private String nextWord; private String lastComments; /** * Creates a new ConfigurationParser for the given String arguments and * the given Properties. */ public ConfigurationParser(String[] args, Properties properties) throws IOException { this(args, null, properties); } /** * Creates a new ConfigurationParser for the given String arguments, * with the given base directory and the given Properties. */ public ConfigurationParser(String[] args, File baseDir, Properties properties) throws IOException { this(new ArgumentWordReader(args, baseDir), properties); } /** * Creates a new ConfigurationParser for the given lines, * with the given base directory and the given Properties. */ public ConfigurationParser(String lines, String description, File baseDir, Properties properties) throws IOException { this(new LineWordReader(new LineNumberReader(new StringReader(lines)), description, baseDir), properties); } /** * Creates a new ConfigurationParser for the given file, with the system * Properties. * @deprecated Temporary code for backward compatibility in Obclipse. */ public ConfigurationParser(File file) throws IOException { this(file, System.getProperties()); } /** * Creates a new ConfigurationParser for the given file and the given * Properties. */ public ConfigurationParser(File file, Properties properties) throws IOException { this(new FileWordReader(file), properties); } /** * Creates a new ConfigurationParser for the given URL and the given * Properties. */ public ConfigurationParser(URL url, Properties properties) throws IOException { this(new FileWordReader(url), properties); } /** * Creates a new ConfigurationParser for the given word reader and the * given Properties. */ public ConfigurationParser(WordReader reader, Properties properties) throws IOException { this.reader = reader; this.properties = properties; readNextWord(); } /** * Parses and returns the configuration. * @param configuration the configuration that is updated as a side-effect. * @throws ParseException if the any of the configuration settings contains * a syntax error. * @throws IOException if an IO error occurs while reading a configuration. */ public void parse(Configuration configuration) throws ParseException, IOException { while (nextWord != null) { lastComments = reader.lastComments(); // First include directives. if (ConfigurationConstants.AT_DIRECTIVE .startsWith(nextWord) || ConfigurationConstants.INCLUDE_DIRECTIVE .startsWith(nextWord)) configuration.lastModified = parseIncludeArgument(configuration.lastModified); else if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE .startsWith(nextWord)) parseBaseDirectoryArgument(); // Then configuration options with or without arguments. else if (ConfigurationConstants.INJARS_OPTION .startsWith(nextWord)) configuration.programJars = parseClassPathArgument(configuration.programJars, false); else if (ConfigurationConstants.OUTJARS_OPTION .startsWith(nextWord)) configuration.programJars = parseClassPathArgument(configuration.programJars, true); else if (ConfigurationConstants.LIBRARYJARS_OPTION .startsWith(nextWord)) configuration.libraryJars = parseClassPathArgument(configuration.libraryJars, false); else if (ConfigurationConstants.RESOURCEJARS_OPTION .startsWith(nextWord)) throw new ParseException(""The '-resourcejars' option is no longer supported. Please use the '-injars' option for all input""); else if (ConfigurationConstants.SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = parseNoArgument(true); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses = parseNoArgument(false); else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION.startsWith(nextWord)) configuration.skipNonPublicLibraryClassMembers = parseNoArgument(false); else if (ConfigurationConstants.TARGET_OPTION .startsWith(nextWord)) configuration.targetClassVersion = parseClassVersion(); else if (ConfigurationConstants.FORCE_PROCESSING_OPTION .startsWith(nextWord)) configuration.lastModified = parseNoArgument(Long.MAX_VALUE); else if (ConfigurationConstants.IF_OPTION .startsWith(nextWord)) configuration.keep = parseIfCondition(configuration.keep); else if (ConfigurationConstants.KEEP_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, true, false, false, null); else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, false, false, null); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, false, null); else if (ConfigurationConstants.KEEP_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, true, false, true, null); else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, false, true, null); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.keep = parseKeepClassSpecificationArguments(configuration.keep, false, true, true, null); else if (ConfigurationConstants.PRINT_SEEDS_OPTION .startsWith(nextWord)) configuration.printSeeds = parseOptionalFile(); // After '-keep'. else if (ConfigurationConstants.KEEP_DIRECTORIES_OPTION .startsWith(nextWord)) configuration.keepDirectories = parseCommaSeparatedList(""directory name"", true, true, false, true, false, true, true, false, false, configuration.keepDirectories); else if (ConfigurationConstants.DONT_SHRINK_OPTION .startsWith(nextWord)) configuration.shrink = parseNoArgument(false); else if (ConfigurationConstants.PRINT_USAGE_OPTION .startsWith(nextWord)) configuration.printUsage = parseOptionalFile(); else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION .startsWith(nextWord)) configuration.whyAreYouKeeping = parseKeepClassSpecificationArguments(configuration.whyAreYouKeeping, false, false, false, null); else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.optimize = parseNoArgument(false); else if (ConfigurationConstants.OPTIMIZATION_PASSES .startsWith(nextWord)) configuration.optimizationPasses = parseIntegerArgument(); else if (ConfigurationConstants.OPTIMIZATIONS .startsWith(nextWord)) configuration.optimizations = parseCommaSeparatedList(""optimization name"", true, false, false, false, false, true, false, false, false, configuration.optimizations); else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoSideEffects = parseAssumeClassSpecificationArguments(configuration.assumeNoSideEffects); else if (ConfigurationConstants.ASSUME_NO_EXTERNAL_SIDE_EFFECTS_OPTION .startsWith(nextWord)) configuration.assumeNoExternalSideEffects = parseAssumeClassSpecificationArguments(configuration.assumeNoExternalSideEffects); else if (ConfigurationConstants.ASSUME_NO_ESCAPING_PARAMETERS_OPTION .startsWith(nextWord)) configuration.assumeNoEscapingParameters = parseAssumeClassSpecificationArguments(configuration.assumeNoEscapingParameters); else if (ConfigurationConstants.ASSUME_NO_EXTERNAL_RETURN_VALUES_OPTION .startsWith(nextWord)) configuration.assumeNoExternalReturnValues = parseAssumeClassSpecificationArguments(configuration.assumeNoExternalReturnValues); else if (ConfigurationConstants.ASSUME_VALUES_OPTION .startsWith(nextWord)) configuration.assumeValues = parseAssumeClassSpecificationArguments(configuration.assumeValues); else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = parseNoArgument(true); else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile(); else if (ConfigurationConstants.APPLY_MAPPING_OPTION .startsWith(nextWord)) configuration.applyMapping = parseFile(); else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.obfuscationDictionary = parseURL(); else if (ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.classObfuscationDictionary = parseURL(); else if (ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION .startsWith(nextWord)) configuration.packageObfuscationDictionary = parseURL(); else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.overloadAggressively = parseNoArgument(true); else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) configuration.useUniqueClassMemberNames = parseNoArgument(true); else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION .startsWith(nextWord)) configuration.useMixedCaseClassNames = parseNoArgument(false); else if (ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION .startsWith(nextWord)) configuration.keepPackageNames = parseCommaSeparatedList(""package name"", true, true, false, false, true, false, false, true, false, configuration.keepPackageNames); else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION .startsWith(nextWord)) configuration.flattenPackageHierarchy = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.DEFAULT_PACKAGE_OPTION .startsWith(nextWord)) configuration.repackageClasses = ClassUtil.internalClassName(parseOptionalArgument()); else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION .startsWith(nextWord)) configuration.keepAttributes = parseCommaSeparatedList(""attribute name"", true, true, false, false, true, false, false, false, false, configuration.keepAttributes); else if (ConfigurationConstants.KEEP_PARAMETER_NAMES_OPTION .startsWith(nextWord)) configuration.keepParameterNames = parseNoArgument(true); else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION .startsWith(nextWord)) configuration.newSourceFileAttribute = parseOptionalArgument(); else if (ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION .startsWith(nextWord)) configuration.adaptClassStrings = parseCommaSeparatedList(""class name"", true, true, false, false, true, false, false, true, false, configuration.adaptClassStrings); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION .startsWith(nextWord)) configuration.adaptResourceFileNames = parseCommaSeparatedList(""resource file name"", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileNames); else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION .startsWith(nextWord)) configuration.adaptResourceFileContents = parseCommaSeparatedList(""resource file name"", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileContents); else if (ConfigurationConstants.DONT_PREVERIFY_OPTION .startsWith(nextWord)) configuration.preverify = parseNoArgument(false); else if (ConfigurationConstants.MICRO_EDITION_OPTION .startsWith(nextWord)) configuration.microEdition = parseNoArgument(true); else if (ConfigurationConstants.ANDROID_OPTION .startsWith(nextWord)) configuration.android = parseNoArgument(true); else if (ConfigurationConstants.VERBOSE_OPTION .startsWith(nextWord)) configuration.verbose = parseNoArgument(true); else if (ConfigurationConstants.DONT_NOTE_OPTION .startsWith(nextWord)) configuration.note = parseCommaSeparatedList(""class name"", true, true, false, false, true, false, false, true, false, configuration.note); else if (ConfigurationConstants.DONT_WARN_OPTION .startsWith(nextWord)) configuration.warn = parseCommaSeparatedList(""class name"", true, true, false, false, true, false, false, true, false, configuration.warn); else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION .startsWith(nextWord)) configuration.ignoreWarnings = parseNoArgument(true); else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION .startsWith(nextWord)) configuration.printConfiguration = parseOptionalFile(); else if (ConfigurationConstants.DUMP_OPTION .startsWith(nextWord)) configuration.dump = parseOptionalFile(); else if (ConfigurationConstants.ADD_CONFIGURATION_DEBUGGING_OPTION .startsWith(nextWord)) configuration.addConfigurationDebugging = parseNoArgument(true); else { throw new ParseException(""Unknown option "" + reader.locationDescription()); } } } /** * Closes the configuration. * @throws IOException if an IO error occurs while closing the configuration. */ public void close() throws IOException { if (reader != null) { reader.close(); } } private long parseIncludeArgument(long lastModified) throws ParseException, IOException { // Read the configuration file name. readNextWord(""configuration file name"", true, true, false); URL baseURL = reader.getBaseURL(); URL url = null; try { // Check if the file name is a valid URL. url = new URL(nextWord); } catch (MalformedURLException ex) {} if (url != null) { reader.includeWordReader(new FileWordReader(url)); } // Is it relative to a URL or to a file? else if (baseURL != null) { url = new URL(baseURL, nextWord); reader.includeWordReader(new FileWordReader(url)); } else { // Is the file a valid resource URL? url = ConfigurationParser.class.getResource(nextWord); if (url != null) { reader.includeWordReader(new FileWordReader(url)); } else { File file = file(nextWord); reader.includeWordReader(new FileWordReader(file)); long fileLastModified = file.lastModified(); if (fileLastModified > lastModified) { lastModified = fileLastModified; } } } readNextWord(); return lastModified; } private void parseBaseDirectoryArgument() throws ParseException, IOException { // Read the base directory name. readNextWord(""base directory name"", true, true, false); reader.setBaseDir(file(nextWord)); readNextWord(); } private ClassPath parseClassPathArgument(ClassPath classPath, boolean isOutput) throws ParseException, IOException { // Create a new List if necessary. if (classPath == null) { classPath = new ClassPath(); } while (true) { // Read the next jar name. readNextWord(""jar or directory name"", true, false, false); // Create a new class path entry. ClassPathEntry entry = new ClassPathEntry(file(nextWord), isOutput); // Read the opening parenthesis or the separator, if any. readNextWord(); // Read the optional filters. if (!configurationEnd() && ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord)) { // Read all filters in an array. List[] filters = new List[7]; int counter = 0; do { // Read the filter. filters[counter++] = parseCommaSeparatedList(""filter"", true, true, true, true, false, true, true, false, false, null); } while (counter < filters.length && ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)); // Make sure there is a closing parenthesis. if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord)) { throw new ParseException(""Expecting separating '"" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ""' or '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""', or closing '"" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + ""' before "" + reader.locationDescription()); } // Set all filters from the array on the entry. entry.setFilter(filters[--counter]); if (counter > 0) { entry.setJarFilter(filters[--counter]); if (counter > 0) { entry.setWarFilter(filters[--counter]); if (counter > 0) { entry.setEarFilter(filters[--counter]); if (counter > 0) { entry.setJmodFilter(filters[--counter]); if (counter > 0) { entry.setZipFilter(filters[--counter]); if (counter > 0) { // For backward compatibility, the apk // filter comes second in the list. entry.setApkFilter(filters[--counter]); if (counter > 0) { // For backward compatibility, the aar // filter comes first in the list. entry.setAarFilter(filters[--counter]); } } } } } } } // Read the separator, if any. readNextWord(); } // Add the entry to the list. classPath.add(entry); if (configurationEnd()) { return classPath; } if (!nextWord.equals(ConfigurationConstants.JAR_SEPARATOR_KEYWORD)) { throw new ParseException(""Expecting class path separator '"" + ConfigurationConstants.JAR_SEPARATOR_KEYWORD + ""' before "" + reader.locationDescription()); } } } private int parseClassVersion() throws ParseException, IOException { // Read the obligatory target. readNextWord(""java version""); int [MASK] = ClassUtil.internalClassVersion(nextWord); if ( [MASK] == 0) { throw new ParseException(""Unsupported java version "" + reader.locationDescription()); } readNextWord(); return [MASK] ; } private int parseIntegerArgument() throws ParseException, IOException { try { // Read the obligatory integer. readNextWord(""integer""); int integer = Integer.parseInt(nextWord); if (integer <= 0) { throw new ParseException(""Expecting value larger than 0, instead of '"" + nextWord + ""' before "" + reader.locationDescription()); } readNextWord(); return integer; } catch (NumberFormatException e) { throw new ParseException(""Expecting integer argument instead of '"" + nextWord + ""' before "" + reader.locationDescription()); } } private URL parseURL() throws ParseException, IOException { // Read the obligatory file name. readNextWord(""file name"", true, true, false); // Make sure the file is properly resolved. URL url = url(nextWord); readNextWord(); return url; } private File parseFile() throws ParseException, IOException { // Read the obligatory file name. readNextWord(""file name"", true, true, false); // Make sure the file is properly resolved. File file = file(nextWord); readNextWord(); return file; } private File parseOptionalFile() throws ParseException, IOException { // Read the optional file name. readNextWord(true, true); // Didn't the user specify a file name? if (configurationEnd()) { return Configuration.STD_OUT; } // Make sure the file is properly resolved. File file = file(nextWord); readNextWord(); return file; } private String parseOptionalArgument() throws IOException { // Read the optional argument. readNextWord(); // Didn't the user specify an argument? if (configurationEnd()) { return """"; } String argument = nextWord; readNextWord(); return argument; } private boolean parseNoArgument(boolean value) throws IOException { readNextWord(); return value; } private long parseNoArgument(long value) throws IOException { readNextWord(); return value; } /** * Parses and adds a conditional class specification to keep other classes * and class members. * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ private List parseIfCondition(List keepClassSpecifications) throws ParseException, IOException { // Read the condition. readNextWord(""keyword '"" + ConfigurationConstants.CLASS_KEYWORD + ""', '"" + JavaConstants.ACC_INTERFACE + ""', or '"" + JavaConstants.ACC_ENUM + ""'"", false, false, true); ClassSpecification condition = parseClassSpecificationArguments(false); // Read the corresponding keep option. if (nextWord == null) { throw new ParseException(""Expecting '-keep' option after '-if' option, before "" + reader.locationDescription()); } if (ConfigurationConstants.KEEP_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, true, false, false, condition); else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, false, false, condition); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, true, false, condition); else if (ConfigurationConstants.KEEP_NAMES_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, true, false, true, condition); else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION .startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, false, true, condition); else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION.startsWith(nextWord)) keepClassSpecifications = parseKeepClassSpecificationArguments(keepClassSpecifications, false, true, true, condition); else { throw new ParseException(""Expecting '-keep' option after '-if' option, before "" + reader.locationDescription()); } return keepClassSpecifications; } /** * Parses and adds a class specification to keep classes and class members. * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ private List parseKeepClassSpecificationArguments(List keepClassSpecifications, boolean markClasses, boolean markConditionally, boolean allowShrinking, ClassSpecification condition) throws ParseException, IOException { // Create a new List if necessary. if (keepClassSpecifications == null) { keepClassSpecifications = new ArrayList(); } // Read and add the keep configuration. keepClassSpecifications.add(parseKeepClassSpecificationArguments(markClasses, markConditionally, allowShrinking, condition)); return keepClassSpecifications; } /** * Parses and returns a class specification to keep classes and class * members. * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ private KeepClassSpecification parseKeepClassSpecificationArguments(boolean markClasses, boolean markConditionally, boolean allowShrinking, ClassSpecification condition) throws ParseException, IOException { boolean markDescriptorClasses = false; boolean markCodeAttributes = false; //boolean allowShrinking = false; boolean allowOptimization = false; boolean allowObfuscation = false; // Read the keep modifiers. while (true) { readNextWord(""keyword '"" + ConfigurationConstants.CLASS_KEYWORD + ""', '"" + JavaConstants.ACC_INTERFACE + ""', or '"" + JavaConstants.ACC_ENUM + ""'"", false, false, true); if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { // Not a comma. Stop parsing the keep modifiers. break; } readNextWord(""keyword '"" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + ""', '"" + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + ""', or '"" + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + ""'""); if (ConfigurationConstants.INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION.startsWith(nextWord)) { markDescriptorClasses = true; } else if (ConfigurationConstants.INCLUDE_CODE_SUBOPTION .startsWith(nextWord)) { markCodeAttributes = true; } else if (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION .startsWith(nextWord)) { allowShrinking = true; } else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION .startsWith(nextWord)) { allowOptimization = true; } else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION .startsWith(nextWord)) { allowObfuscation = true; } else { throw new ParseException(""Expecting keyword '"" + ConfigurationConstants.INCLUDE_DESCRIPTOR_CLASSES_SUBOPTION + ""', '"" + ConfigurationConstants.INCLUDE_CODE_SUBOPTION + ""', '"" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + ""', '"" + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION + ""', or '"" + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + ""' before "" + reader.locationDescription()); } } // Read the class configuration. ClassSpecification classSpecification = parseClassSpecificationArguments(false); // Create and return the keep configuration. return new KeepClassSpecification(markClasses, markConditionally, markDescriptorClasses, markCodeAttributes, allowShrinking, allowOptimization, allowObfuscation, condition, classSpecification); } /** * Parses and adds a class specification for optimization assumptions. * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ private List parseAssumeClassSpecificationArguments(List classSpecifications) throws ParseException, IOException { // Create a new List if necessary. if (classSpecifications == null) { classSpecifications = new ArrayList(); } readNextWord(""keyword '"" + ConfigurationConstants.CLASS_KEYWORD + ""', '"" + JavaConstants.ACC_INTERFACE + ""', or '"" + JavaConstants.ACC_ENUM + ""'"", false, false, true); // Read and add the class configuration. classSpecifications.add(parseClassSpecificationArguments(true)); return classSpecifications; } // Added for compatibility with older versions of ProGuard (see PGD-755). public ClassSpecification parseClassSpecificationArguments() throws ParseException, IOException { return parseClassSpecificationArguments(false); } /** * Parses and returns a class specification. * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ public ClassSpecification parseClassSpecificationArguments(boolean allowValues) throws ParseException, IOException { // Clear the annotation type. String annotationType = null; // Clear the class access modifiers. int requiredSetClassAccessFlags = 0; int requiredUnsetClassAccessFlags = 0; // Parse the class annotations and access modifiers until the class keyword. while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord)) { // Strip the negating sign, if any. boolean negated = nextWord.startsWith(ConfigurationConstants.NEGATOR_KEYWORD); String strippedWord = negated ? nextWord.substring(1) : nextWord; // Parse the class access modifiers. int accessFlag = strippedWord.equals(JavaConstants.ACC_PUBLIC) ? ClassConstants.ACC_PUBLIC : strippedWord.equals(JavaConstants.ACC_FINAL) ? ClassConstants.ACC_FINAL : strippedWord.equals(JavaConstants.ACC_INTERFACE) ? ClassConstants.ACC_INTERFACE : strippedWord.equals(JavaConstants.ACC_ABSTRACT) ? ClassConstants.ACC_ABSTRACT : strippedWord.equals(JavaConstants.ACC_SYNTHETIC) ? ClassConstants.ACC_SYNTHETIC : strippedWord.equals(JavaConstants.ACC_ANNOTATION) ? ClassConstants.ACC_ANNOTATION : strippedWord.equals(JavaConstants.ACC_ENUM) ? ClassConstants.ACC_ENUM : unknownAccessFlag(); // Is it an annotation modifier? if (accessFlag == ClassConstants.ACC_ANNOTATION) { readNextWord(""annotation type or keyword '"" + JavaConstants.ACC_INTERFACE + ""'"", false, false, false); // Is the next word actually an annotation type? if (!nextWord.equals(JavaConstants.ACC_INTERFACE) && !nextWord.equals(JavaConstants.ACC_ENUM) && !nextWord.equals(ConfigurationConstants.CLASS_KEYWORD)) { // Parse the annotation type. annotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList(""annotation type"", false, false, false, false, true, false, false, false, true, null), false); // Continue parsing the access modifier that we just read // in the next cycle. continue; } // Otherwise just handle the annotation modifier. } if (!negated) { requiredSetClassAccessFlags |= accessFlag; } else { requiredUnsetClassAccessFlags |= accessFlag; } if ((requiredSetClassAccessFlags & requiredUnsetClassAccessFlags) != 0) { throw new ParseException(""Conflicting class access modifiers for '"" + strippedWord + ""' before "" + reader.locationDescription()); } if (strippedWord.equals(JavaConstants.ACC_INTERFACE) || strippedWord.equals(JavaConstants.ACC_ENUM) || strippedWord.equals(ConfigurationConstants.CLASS_KEYWORD)) { // The interface or enum keyword. Stop parsing the class flags. break; } // Should we read the next word? if (accessFlag != ClassConstants.ACC_ANNOTATION) { readNextWord(""keyword '"" + ConfigurationConstants.CLASS_KEYWORD + ""', '"" + JavaConstants.ACC_INTERFACE + ""', or '"" + JavaConstants.ACC_ENUM + ""'"", false, false, true); } } // Parse the class name part. String externalClassName = ListUtil.commaSeparatedString( parseCommaSeparatedList(""class name or interface name"", true, false, false, false, true, false, false, false, false, null), false); // For backward compatibility, allow a single ""*"" wildcard to match any // class. String className = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalClassName) ? null : ClassUtil.internalClassName(externalClassName); // Clear the annotation type and the class name of the extends part. String extendsAnnotationType = null; String extendsClassName = null; if (!configurationEnd()) { // Parse 'implements ...' or 'extends ...' part, if any. if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord) || ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord)) { readNextWord(""class name or interface name"", false, false, true); // Parse the annotation type, if any. if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) { extendsAnnotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList(""annotation type"", true, false, false, false, true, false, false, false, true, null), false); } String externalExtendsClassName = ListUtil.commaSeparatedString( parseCommaSeparatedList(""class name or interface name"", false, false, false, false, true, false, false, false, false, null), false); extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalExtendsClassName) ? null : ClassUtil.internalClassName(externalExtendsClassName); } } // Create the basic class specification. ClassSpecification classSpecification = new ClassSpecification(lastComments, requiredSetClassAccessFlags, requiredUnsetClassAccessFlags, annotationType, className, extendsAnnotationType, extendsClassName); // Now add any class members to this class specification. if (!configurationEnd()) { // Check the class member opening part. if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord)) { throw new ParseException(""Expecting opening '"" + ConfigurationConstants.OPEN_KEYWORD + ""' at "" + reader.locationDescription()); } // Parse all class members. while (true) { readNextWord(""class member description"" + "" or closing '"" + ConfigurationConstants.CLOSE_KEYWORD + ""'"", false, false, true); if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD)) { // The closing brace. Stop parsing the class members. readNextWord(); break; } parseMemberSpecificationArguments(externalClassName, allowValues, classSpecification); } } return classSpecification; } /** * Parses and adds a class member specification. * @throws ParseException if the class specification contains a syntax error. * @throws IOException if an IO error occurs while reading the class * specification. */ private void parseMemberSpecificationArguments(String externalClassName, boolean allowValues, ClassSpecification classSpecification) throws ParseException, IOException { // Clear the annotation name. String annotationType = null; // Parse the class member access modifiers, if any. int requiredSetMemberAccessFlags = 0; int requiredUnsetMemberAccessFlags = 0; while (!configurationEnd(true)) { // Parse the annotation type, if any. if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) { annotationType = ListUtil.commaSeparatedString( parseCommaSeparatedList(""annotation type"", true, false, false, false, true, false, false, false, true, null), false); continue; } String strippedWord = nextWord.startsWith(""!"") ? nextWord.substring(1) : nextWord; // Parse the class member access modifiers. int accessFlag = strippedWord.equals(JavaConstants.ACC_PUBLIC) ? ClassConstants.ACC_PUBLIC : strippedWord.equals(JavaConstants.ACC_PRIVATE) ? ClassConstants.ACC_PRIVATE : strippedWord.equals(JavaConstants.ACC_PROTECTED) ? ClassConstants.ACC_PROTECTED : strippedWord.equals(JavaConstants.ACC_STATIC) ? ClassConstants.ACC_STATIC : strippedWord.equals(JavaConstants.ACC_FINAL) ? ClassConstants.ACC_FINAL : strippedWord.equals(JavaConstants.ACC_SYNCHRONIZED) ? ClassConstants.ACC_SYNCHRONIZED : strippedWord.equals(JavaConstants.ACC_VOLATILE) ? ClassConstants.ACC_VOLATILE : strippedWord.equals(JavaConstants.ACC_TRANSIENT) ? ClassConstants.ACC_TRANSIENT : strippedWord.equals(JavaConstants.ACC_BRIDGE) ? ClassConstants.ACC_BRIDGE : strippedWord.equals(JavaConstants.ACC_VARARGS) ? ClassConstants.ACC_VARARGS : strippedWord.equals(JavaConstants.ACC_NATIVE) ? ClassConstants.ACC_NATIVE : strippedWord.equals(JavaConstants.ACC_ABSTRACT) ? ClassConstants.ACC_ABSTRACT : strippedWord.equals(JavaConstants.ACC_STRICT) ? ClassConstants.ACC_STRICT : strippedWord.equals(JavaConstants.ACC_SYNTHETIC) ? ClassConstants.ACC_SYNTHETIC : 0; if (accessFlag == 0) { // Not a class member access modifier. Stop parsing them. break; } if (strippedWord.equals(nextWord)) { requiredSetMemberAccessFlags |= accessFlag; } else { requiredUnsetMemberAccessFlags |= accessFlag; } // Make sure the user doesn't try to set and unset the same // access flags simultaneously. if ((requiredSetMemberAccessFlags & requiredUnsetMemberAccessFlags) != 0) { throw new ParseException(""Conflicting class member access modifiers for "" + reader.locationDescription()); } readNextWord(""class member description""); } // Parse the class member type and name part. // Did we get a special wildcard? if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord) || ConfigurationConstants.ANY_FIELD_KEYWORD .equals(nextWord) || ConfigurationConstants.ANY_METHOD_KEYWORD .equals(nextWord)) { // Act according to the type of wildcard.. if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord)) { checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord)) { checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord)) { checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, null, null)); } // We still have to read the closing separator. readNextWord(""separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""'""); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException(""Expecting separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""' before "" + reader.locationDescription()); } } else { // Make sure we have a proper type. checkJavaIdentifier(""java type""); String type = nextWord; readNextWord(""class member name""); String name = nextWord; // Did we get just one word before the opening parenthesis? if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name)) { // This must be a constructor then. // Make sure the type is a proper constructor name. if (!(type.equals(ClassConstants.METHOD_NAME_INIT) || type.equals(externalClassName) || type.equals(ClassUtil.externalShortClassName(externalClassName)))) { throw new ParseException(""Expecting type and name "" + ""instead of just '"" + type + ""' before "" + reader.locationDescription()); } // Assign the fixed constructor type and name. type = JavaConstants.TYPE_VOID; name = ClassConstants.METHOD_NAME_INIT; } else { // It's not a constructor. // Make sure we have a proper name. checkJavaIdentifier(""class member name""); // Read the opening parenthesis or the separating // semi-colon. readNextWord(""opening '"" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ""' or separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""'""); } // Are we looking at a field, a method, or something else? if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { // It's a field. checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // We already have a field descriptor. String descriptor = ClassUtil.internalType(type); // Add the field. classSpecification.addField( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor)); } else if (allowValues && (ConfigurationConstants.EQUAL_KEYWORD.equals(nextWord) || ConfigurationConstants.RETURN_KEYWORD.equals(nextWord))) { // It's a field with a value. checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // We already have a field descriptor. String descriptor = ClassUtil.internalType(type); // Read the constant. Number[] values = parseValues(type, descriptor); // Read the separator after the constant. readNextWord(""separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""'""); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException(""Expecting separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""' before "" + reader.locationDescription()); } // Add the field. classSpecification.addField( new MemberValueSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor, values)); } else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord)) { // It's a method. checkMethodAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // Parse the method arguments. String descriptor = ClassUtil.internalMethodDescriptor(type, parseCommaSeparatedList(""argument"", true, true, true, false, true, false, false, false, false, null)); if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord)) { throw new ParseException(""Expecting separating '"" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ""' or closing '"" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + ""' before "" + reader.locationDescription()); } // Read the separator after the closing parenthesis. readNextWord(""separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""'""); if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { // Add the plain method. classSpecification.addMethod( new MemberSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor)); } else if (allowValues && (ConfigurationConstants.EQUAL_KEYWORD.equals(nextWord) || ConfigurationConstants.RETURN_KEYWORD.equals(nextWord))) { // It's a method with a value. checkFieldAccessFlags(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags); // Read the constant. Number[] values = parseValues(type, ClassUtil.internalType(type)); // Read the separator after the constant. readNextWord(""separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""'""); if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) { throw new ParseException(""Expecting separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""' before "" + reader.locationDescription()); } // Add the method. classSpecification.addMethod( new MemberValueSpecification(requiredSetMemberAccessFlags, requiredUnsetMemberAccessFlags, annotationType, name, descriptor, values)); } else { throw new ParseException(""Expecting separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""' before "" + reader.locationDescription()); } } else { // It doesn't look like a field or a method. throw new ParseException(""Expecting opening '"" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ""' or separator '"" + ConfigurationConstants.SEPARATOR_KEYWORD + ""' before "" + reader.locationDescription()); } } } /** * Reads a value or value range of the given primitive type. * For example, values ""123"" or ""100..199"" of type ""int"" (""I""). */ private Number[] parseValues(String externalType, String internalType) throws ParseException, IOException { readNextWord(externalType + "" constant""); int rangeIndex = nextWord.indexOf(ConfigurationConstants.RANGE_KEYWORD); return rangeIndex >= 0 ? new Number[] { parseValue(externalType, internalType, nextWord.substring(0, rangeIndex)), parseValue(externalType, internalType, nextWord.substring(rangeIndex + ConfigurationConstants.RANGE_KEYWORD.length())) } : new Number[] { parseValue(externalType, internalType, nextWord) }; } /** * Parses the given string as a value of the given primitive type. * For example, value ""123"" of type ""int"" (""I""). * For example, value ""true"" of type ""boolean"" (""Z""), returned as 1. */ private Number parseValue(String externalType, String internalType, String string) throws ParseException { try { string = replaceSystemProperties(string); switch (internalType.charAt(0)) { case ClassConstants.TYPE_BOOLEAN: { return parseBoolean(string); } case ClassConstants.TYPE_BYTE: case ClassConstants.TYPE_CHAR: case ClassConstants.TYPE_SHORT: case ClassConstants.TYPE_INT: { return Integer.decode(string); } //case ClassConstants.TYPE_LONG: //{ // return Long.decode(string); //} //case ClassConstants.TYPE_FLOAT: //{ // return Float.valueOf(string); //} //case ClassConstants.TYPE_DOUBLE: //{ // return Double.valueOf(string); //} default: { throw new ParseException(""Can't handle '"" + externalType + ""' constant "" + reader.locationDescription()); } } } catch (NumberFormatException e) { throw new ParseException(""Can't parse "" + externalType + "" constant "" + reader.locationDescription()); } } /** * Parses the given boolean string as an integer (0 or 1). */ private Integer parseBoolean(String string) throws ParseException { if (ConfigurationConstants.FALSE_KEYWORD.equals(nextWord)) { return Integer.valueOf(0); } else if (ConfigurationConstants.TRUE_KEYWORD.equals(nextWord)) { return Integer.valueOf(1); } else { throw new ParseException(""Unknown boolean constant "" + reader.locationDescription()); } } /** * Reads a comma-separated list of Lists of java identifiers or of file * names. */ private List parseCommaSeparatedLists(String expectedDescription, boolean readFirstWord, boolean allowEmptyList, boolean expectClosingParenthesis, boolean isFileName, boolean checkJavaIdentifiers, boolean allowGenerics, boolean replaceSystemProperties, boolean replaceExternalClassNames, boolean replaceExternalTypes, List list) throws ParseException, IOException { if (list == null) { list = new ArrayList(); } // Parse a new list and add it to our list. list.add(parseCommaSeparatedList(expectedDescription, readFirstWord, allowEmptyList, expectClosingParenthesis, isFileName, checkJavaIdentifiers, allowGenerics, replaceSystemProperties, replaceExternalClassNames, replaceExternalTypes, null)); return list; } /** * Reads a comma-separated list of java identifiers or of file names. * Examples of invocation arguments: * * expected read allow expect is check allow replace replace replace * description First empty Closing File Java Generic System Extern Extern * Word List Paren Name Id Prop Class Types * ---------------------------------------------------------------------------------- * (""directory name"", true, true, false, true, false, true, true, false, false, ...) * (""optimization"", true, false, false, false, false, true, false, false, false, ...) * (""package name"", true, true, false, false, true, false, false, true, false, ...) * (""attribute name"", true, true, false, false, true, false, false, false, false, ...) * (""class name"", true, true, false, false, true, false, false, true, false, ...) * (""filter"", true, true, true, true, false, true, true, false, false, ...) * (""annotation "", false, false, false, false, true, false, false, false, true, ...) * (""class name "", true, false, false, false, true, false, false, false, false, ...) * (""annotation "", true, false, false, false, true, false, false, false, true, ...) * (""class name "", false, false, false, false, true, false, false, false, false, ...) * (""annotation "", true, false, false, false, true, false, false, false, true, ...) * (""argument"", true, true, true, false, true, false, false, false, false, ...) */ private List parseCommaSeparatedList(String expectedDescription, boolean readFirstWord, boolean allowEmptyList, boolean expectClosingParenthesis, boolean isFileName, boolean checkJavaIdentifiers, boolean allowGenerics, boolean replaceSystemProperties, boolean replaceExternalClassNames, boolean replaceExternalTypes, List list) throws ParseException, IOException { if (list == null) { list = new ArrayList(); } if (readFirstWord) { if (!allowEmptyList) { // Read the first list entry. readNextWord(expectedDescription, isFileName, true, false); } else if (expectClosingParenthesis) { // Read the first list entry. readNextWord(expectedDescription, isFileName, true, false); // Return if the entry is actually empty (an empty file name or // a closing parenthesis). if (nextWord.length() == 0) { // Read the closing parenthesis readNextWord(""closing '"" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + ""'""); return list; } else if (nextWord.equals(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD)) { return list; } } else { // Read the first list entry, if there is any. readNextWord(isFileName, true); // Check if the list is empty. if (configurationEnd()) { return list; } } } while (true) { if (checkJavaIdentifiers) { checkJavaIdentifier(""java type"", allowGenerics); } if (replaceSystemProperties) { nextWord = replaceSystemProperties(nextWord); } if (replaceExternalClassNames) { nextWord = ClassUtil.internalClassName(nextWord); } if (replaceExternalTypes) { nextWord = ClassUtil.internalType(nextWord); } list.add(nextWord); if (expectClosingParenthesis) { // Read a comma (or a closing parenthesis, or a different word). readNextWord(""separating '"" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD + ""' or closing '"" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD + ""'""); } else { // Read a comma (or a different word). readNextWord(); } if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord)) { return list; } // Read the next list entry. readNextWord(expectedDescription, isFileName, true, false); } } /** * Throws a ParseException for an unexpected keyword. */ private int unknownAccessFlag() throws ParseException { throw new ParseException(""Unexpected keyword "" + reader.locationDescription()); } /** * Creates a properly resolved URL, based on the given word. */ private URL url(String word) throws ParseException, MalformedURLException { String fileName = replaceSystemProperties(word); URL url; try { // Check if the file name is a valid URL. url = new URL(fileName); return url; } catch (MalformedURLException ex) {} // Is it relative to a URL or to a file? URL baseURL = reader.getBaseURL(); if (baseURL != null) { url = new URL(baseURL, fileName); } else { // Is the file a valid resource URL? url = ConfigurationParser.class.getResource(fileName); if (url == null) { File file = new File(fileName); // Try to get an absolute file. if (!file.isAbsolute()) { file = new File(reader.getBaseDir(), fileName); } url = file.toURI().toURL(); } } return url; } /** * Creates a properly resolved File, based on the given word. */ private File file(String word) throws ParseException { String fileName = replaceSystemProperties(word); File file = new File(fileName); // Try to get an absolute file. if (!file.isAbsolute()) { file = new File(reader.getBaseDir(), fileName); } return file; } /** * Replaces any properties in the given word by their values. * For instance, the substring """" is replaced by its value. */ private String replaceSystemProperties(String word) throws ParseException { int fromIndex = 0; while (true) { fromIndex = word.indexOf(ConfigurationConstants.OPEN_SYSTEM_PROPERTY, fromIndex); if (fromIndex < 0) { break; } int toIndex = word.indexOf(ConfigurationConstants.CLOSE_SYSTEM_PROPERTY, fromIndex+1); if (toIndex < 0) { break; } String propertyName = word.substring(fromIndex+1, toIndex); String propertyValue = properties.getProperty(propertyName); if (propertyValue == null) { throw new ParseException(""Value of system property '"" + propertyName + ""' is undefined in "" + reader.locationDescription()); } word = word.substring(0, fromIndex) + propertyValue + word.substring(toIndex+1); fromIndex += propertyValue.length(); } return word; } /** * Reads the next word of the configuration in the 'nextWord' field, * throwing an exception if there is no next word. */ private void readNextWord(String expectedDescription) throws ParseException, IOException { readNextWord(expectedDescription, false, false, false); } /** * Reads the next word of the configuration in the 'nextWord' field, * throwing an exception if there is no next word. */ private void readNextWord(String expectedDescription, boolean isFileName, boolean expectSingleFile, boolean expectingAtCharacter) throws ParseException, IOException { readNextWord(isFileName, expectSingleFile); if (configurationEnd(expectingAtCharacter)) { throw new ParseException(""Expecting "" + expectedDescription + "" before "" + reader.locationDescription()); } } /** * Reads the next word of the configuration in the 'nextWord' field. */ private void readNextWord() throws IOException { readNextWord(false, false); } /** * Reads the next word of the configuration in the 'nextWord' field. */ private void readNextWord(boolean isFileName, boolean expectSingleFile) throws IOException { nextWord = reader.nextWord(isFileName, expectSingleFile); } /** * Returns whether the end of the configuration has been reached. */ private boolean configurationEnd() { return configurationEnd(false); } /** * Returns whether the end of the configuration has been reached. */ private boolean configurationEnd(boolean expectingAtCharacter) { return nextWord == null || nextWord.startsWith(ConfigurationConstants.OPTION_PREFIX) || (!expectingAtCharacter && nextWord.equals(ConfigurationConstants.AT_DIRECTIVE)); } /** * Checks whether the given word is a valid Java identifier and throws * a ParseException if it isn't. Wildcard characters are accepted. */ private void checkJavaIdentifier(String expectedDescription) throws ParseException { checkJavaIdentifier(expectedDescription, true); } /** * Checks whether the given word is a valid Java identifier and throws * a ParseException if it isn't. Wildcard characters are accepted. */ private void checkJavaIdentifier(String expectedDescription, boolean allowGenerics) throws ParseException { if (!isJavaIdentifier(nextWord)) { throw new ParseException(""Expecting "" + expectedDescription + "" before "" + reader.locationDescription()); } if (!allowGenerics && containsGenerics(nextWord)) { throw new ParseException(""Generics are not allowed (erased) in "" + expectedDescription + "" "" + reader.locationDescription()); } } /** * Returns whether the given word is a valid Java identifier. * Wildcard characters are accepted. */ private boolean isJavaIdentifier(String word) { if (word.length() == 0) { return false; } for (int index = 0; index < word.length(); index++) { char c = word.charAt(index); if (!(Character.isJavaIdentifierPart(c) || c == '.' || c == '[' || c == ']' || c == '<' || c == '>' || c == '-' || c == '!' || c == '*' || c == '?' || c == '%')) { return false; } } return true; } /** * Returns whether the given word contains angle brackets around * a non-digit string. */ private boolean containsGenerics(String word) { int index = 0; while (true) { // Can we find an opening angular bracket? int openIndex = word.indexOf(ClassConstants.TYPE_GENERIC_START, index); if (openIndex < 0) { return false; } // Can we find a corresponding closing angular bracket? int closeIndex = word.indexOf(ClassConstants.TYPE_GENERIC_END, openIndex + 1); if (closeIndex < 0) { return false; } try { // Is it just a reference to a wildcard? Integer.parseInt(word.substring(openIndex + 1, closeIndex)); } catch (NumberFormatException e) { // It's not; it's really a generic type. return true; } index = closeIndex + 1; } } /** * Checks whether the given access flags are valid field access flags, * throwing a ParseException if they aren't. */ private void checkFieldAccessFlags(int requiredSetMemberAccessFlags, int requiredUnsetMemberAccessFlags) throws ParseException { if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~ClassConstants.VALID_ACC_FIELD) != 0) { throw new ParseException(""Invalid method access modifier for field before "" + reader.locationDescription()); } } /** * Checks whether the given access flags are valid method access flags, * throwing a ParseException if they aren't. */ private void checkMethodAccessFlags(int requiredSetMemberAccessFlags, int requiredUnsetMemberAccessFlags) throws ParseException { if (((requiredSetMemberAccessFlags | requiredUnsetMemberAccessFlags) & ~ClassConstants.VALID_ACC_METHOD) != 0) { throw new ParseException(""Invalid field access modifier for method before "" + reader.locationDescription()); } } /** * A main method for testing configuration parsing. */ public static void main(String[] args) { try { ConfigurationParser parser = new ConfigurationParser(args, System.getProperties()); try { parser.parse(new Configuration()); } catch (ParseException ex) { ex.printStackTrace(); } finally { parser.close(); } } catch (IOException ex) { ex.printStackTrace(); } } } ","classVersion " "/* * Copyright (C) 2011-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * This file is part of greenDAO Generator. * * greenDAO Generator is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * greenDAO Generator is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with greenDAO Generator. If not, see . */ package org.greenrobot.greendao.generator; import org.greenrobot.greendao.generator.Property.PropertyBuilder; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * Model class for an entity: a Java data object mapped to a data base table. A new entity is added to a {@link Schema} * by the method {@link Schema#addEntity(String)} (there is no public constructor for {@link Entity} itself).
*
Use the various addXXX methods to add entity properties, indexes, and relations to other entities (addToOne, * addToMany).

There are further configuration possibilities:

  • {@link * Entity#implementsInterface(String...)} and {@link #implementsSerializable()} to specify interfaces the entity will * implement
  • {@link #setSuperclass(String)} to specify a class of which the entity will extend from
  • *
  • Various setXXX methods
* * @see Modelling Entities (Documentation page) * @see Relations (Documentation page) */ @SuppressWarnings(""unused"") public class Entity { private final Schema schema; private final String className; private final List properties; private List propertiesColumns; private final List propertiesPk; private final List propertiesNonPk; private final Set propertyNames; private final List indexes; private final List multiIndexes; private final List toOneRelations; private final List toManyRelations; private final List incomingToManyRelations; private final Collection additionalImportsEntity; private final Collection additionalImportsDao; private final List interfacesToImplement; private final List contentProviders; private String dbName; private boolean nonDefaultDbName; private String classNameDao; private String classNameTest; private String javaPackage; private String javaPackageDao; private String javaPackageTest; private Property pkProperty; private String pkType; private String superclass; private String javaDoc; private String codeBeforeClass; private boolean protobuf; private boolean constructors; private boolean skipGeneration; private boolean skipGenerationTest; private boolean skipCreationInDb; private Boolean active; private Boolean hasKeepSections; Entity(Schema schema, String className) { this.schema = schema; this.className = className; properties = new ArrayList<>(); propertiesPk = new ArrayList<>(); propertiesNonPk = new ArrayList<>(); propertyNames = new HashSet<>(); indexes = new ArrayList<>(); multiIndexes = new ArrayList<>(); toOneRelations = new ArrayList<>(); toManyRelations = new ArrayList<>(); incomingToManyRelations = new ArrayList<>(); additionalImportsEntity = new TreeSet<>(); additionalImportsDao = new TreeSet<>(); interfacesToImplement = new ArrayList<>(); contentProviders = new ArrayList<>(); constructors = true; } public PropertyBuilder addBooleanProperty(String propertyName) { return addProperty(PropertyType.Boolean, propertyName); } public PropertyBuilder addByteProperty(String propertyName) { return addProperty(PropertyType.Byte, propertyName); } public PropertyBuilder addShortProperty(String propertyName) { return addProperty(PropertyType.Short, propertyName); } public PropertyBuilder addIntProperty(String propertyName) { return addProperty(PropertyType.Int, propertyName); } public PropertyBuilder addLongProperty(String propertyName) { return addProperty(PropertyType.Long, propertyName); } public PropertyBuilder addFloatProperty(String propertyName) { return addProperty(PropertyType.Float, propertyName); } public PropertyBuilder addDoubleProperty(String propertyName) { return addProperty(PropertyType.Double, propertyName); } public PropertyBuilder addByteArrayProperty(String propertyName) { return addProperty(PropertyType.ByteArray, propertyName); } public PropertyBuilder addStringProperty(String propertyName) { return addProperty(PropertyType.String, propertyName); } public PropertyBuilder addDateProperty(String propertyName) { return addProperty(PropertyType.Date, propertyName); } public PropertyBuilder addProperty(PropertyType propertyType, String propertyName) { if (!propertyNames.add(propertyName)) { throw new RuntimeException(""Property already defined: "" + propertyName); } PropertyBuilder builder = new PropertyBuilder(schema, this, propertyType, propertyName); properties.add(builder.getProperty()); return builder; } /** Adds a standard _id column required by standard Android classes, e.g. list adapters. */ public PropertyBuilder addIdProperty() { PropertyBuilder builder = addLongProperty(""id""); builder.dbName(""_id"").primaryKey(); return builder; } /** Adds a to-many relationship; the target entity is joined to the PK property of this entity (typically the ID). */ public ToMany addToMany(Entity target, Property targetProperty) { Property[] targetProperties = {targetProperty}; return addToMany(null, target, targetProperties); } /** * Convenience method for {@link Entity#addToMany(Entity, Property)} with a subsequent call to {@link * ToMany#setName(String)}. */ public ToMany addToMany(Entity target, Property targetProperty, String name) { ToMany toMany = addToMany(target, targetProperty); toMany.setName(name); return toMany; } /** * Add a to-many relationship; the target entity is joined using the given target property (of the target entity) * and given source property (of this entity). */ public ToMany addToMany(Property sourceProperty, Entity target, Property targetProperty) { Property[] sourceProperties = {sourceProperty}; Property[] targetProperties = {targetProperty}; return addToMany(sourceProperties, target, targetProperties); } public ToMany addToMany(Property[] sourceProperties, Entity target, Property[] targetProperties) { if (protobuf) { throw new IllegalStateException(""Protobuf entities do not support relations, currently""); } ToMany toMany = new ToMany(schema, this, sourceProperties, target, targetProperties); toManyRelations.add(toMany); target.incomingToManyRelations.add(toMany); return toMany; } public ToManyWithJoinEntity addToMany(Entity target, Entity joinEntity, Property id1, Property id2) { ToManyWithJoinEntity toMany = new ToManyWithJoinEntity(schema, this, target, joinEntity, id1, id2); toManyRelations.add(toMany); target.incomingToManyRelations.add(toMany); return toMany; } /** * Adds a to-one relationship to the given target entity using the given given foreign key property (which belongs * to this entity). */ public ToOne addToOne(Entity target, Property fkProperty) { if (protobuf) { throw new IllegalStateException(""Protobuf entities do not support realtions, currently""); } Property[] fkProperties = {fkProperty}; ToOne toOne = new ToOne(schema, this, target, fkProperties, true); toOneRelations.add(toOne); return toOne; } /** Convenience for {@link #addToOne(Entity, Property)} with a subsequent call to {@link ToOne#setName(String)}. */ public ToOne addToOne(Entity target, Property fkProperty, String name) { ToOne toOne = addToOne(target, fkProperty); toOne.setName(name); return toOne; } public ToOne addToOneWithoutProperty(String name, Entity target, String fkColumnName) { return addToOneWithoutProperty(name, target, fkColumnName, false, false); } public ToOne addToOneWithoutProperty(String name, Entity target, String fkColumnName, boolean notNull, boolean unique) { PropertyBuilder propertyBuilder = new PropertyBuilder(schema, this, null, name); if (notNull) { propertyBuilder.notNull(); } if (unique) { propertyBuilder.unique(); } propertyBuilder.dbName(fkColumnName); Property column = propertyBuilder.getProperty(); Property[] fkColumns = {column}; ToOne toOne = new ToOne(schema, this, target, fkColumns, false); toOne.setName(name); toOneRelations.add(toOne); return toOne; } protected void addIncomingToMany(ToMany toMany) { incomingToManyRelations.add(toMany); } public ContentProvider addContentProvider() { List entities = new ArrayList<>(); entities.add(this); ContentProvider contentProvider = new ContentProvider(schema, entities); contentProviders.add(contentProvider); return contentProvider; } /** Adds a new index to the entity. */ public Entity addIndex(Index index) { indexes.add(index); return this; } public Entity addImport(String additionalImport) { additionalImportsEntity.add(additionalImport); return this; } /** The entity is represented by a protocol buffers object. Requires some special actions like using builders. */ Entity useProtobuf() { protobuf = true; return this; } public boolean isProtobuf() { return protobuf; } public Schema getSchema() { return schema; } public String getDbName() { return dbName; } @Deprecated /** * @deprecated Use setDbName */ public void setTableName(String tableName) { setDbName(tableName); } public void setDbName(String dbName) { this.dbName = dbName; this.nonDefaultDbName = dbName != null; } public String getClassName() { return className; } public List getProperties() { return properties; } public List getPropertiesColumns() { return propertiesColumns; } public String getJavaPackage() { return javaPackage; } public void setJavaPackage(String javaPackage) { this.javaPackage = javaPackage; } public String getJavaPackageDao() { return javaPackageDao; } public void setJavaPackageDao(String javaPackageDao) { this.javaPackageDao = javaPackageDao; } public String getClassNameDao() { return classNameDao; } public void setClassNameDao(String classNameDao) { this.classNameDao = classNameDao; } public String getClassNameTest() { return classNameTest; } public void setClassNameTest(String classNameTest) { this.classNameTest = classNameTest; } public String getJavaPackageTest() { return javaPackageTest; } public void setJavaPackageTest(String javaPackageTest) { this.javaPackageTest = javaPackageTest; } /** Internal property used by templates, don't use during entity definition. */ public List getPropertiesPk() { return propertiesPk; } /** Internal property used by templates, don't use during entity definition. */ public List getPropertiesNonPk() { return propertiesNonPk; } /** Internal property used by templates, don't use during entity definition. */ public Property getPkProperty() { return pkProperty; } public List getIndexes() { return indexes; } /** Internal property used by templates, don't use during entity definition. */ public String getPkType() { return pkType; } public boolean isConstructors() { return constructors; } /** Flag to define if constructors should be generated. */ public void setConstructors(boolean constructors) { this.constructors = constructors; } public boolean isSkipGeneration() { return skipGeneration; } /** * Flag if the entity's code generation should be skipped. E.g. if you need to change the class after initial * generation. */ public void setSkipGeneration(boolean skipGeneration) { this.skipGeneration = skipGeneration; } @Deprecated /** * @deprecated Use setSkipCreationInDb */ public void setSkipTableCreation(boolean skipTableCreation) { setSkipCreationInDb(skipTableCreation); } /** Flag if CREATE and DROP TABLE scripts should be skipped in Dao. */ public void setSkipCreationInDb(boolean skipCreationInDb) { this.skipCreationInDb = skipCreationInDb; } public boolean isSkipCreationInDb() { return skipCreationInDb; } public boolean isSkipGenerationTest() { return skipGenerationTest; } public void setSkipGenerationTest(boolean skipGenerationTest) { this.skipGenerationTest = skipGenerationTest; } public List getToOneRelations() { return toOneRelations; } public List getToManyRelations() { return toManyRelations; } public List getIncomingToManyRelations() { return incomingToManyRelations; } /** * Entities with relations are active, but this method allows to make the entities active even if it does not have * relations. */ public void setActive(Boolean active) { this.active = active; } public Boolean getActive() { return active; } public Boolean getHasKeepSections() { return hasKeepSections; } public Collection getAdditionalImportsEntity() { return additionalImportsEntity; } public Collection getAdditionalImportsDao() { return additionalImportsDao; } public void setHasKeepSections(Boolean hasKeepSections) { this.hasKeepSections = hasKeepSections; } public List getInterfacesToImplement() { return interfacesToImplement; } public List getContentProviders() { return contentProviders; } public void implementsInterface(String... interfaces) { for (String interfaceToImplement : interfaces) { if (interfacesToImplement.contains(interfaceToImplement)) { throw new RuntimeException(""Interface defined more than once: "" + interfaceToImplement); } interfacesToImplement.add(interfaceToImplement); } } public void implementsSerializable() { interfacesToImplement.add(""java.io.Serializable""); } public String getSuperclass() { return superclass; } public void setSuperclass(String [MASK] ) { this.superclass = [MASK] ; } public String getJavaDoc() { return javaDoc; } public void setJavaDoc(String javaDoc) { this.javaDoc = DaoUtil.checkConvertToJavaDoc(javaDoc, """"); } public String getCodeBeforeClass() { return codeBeforeClass; } public void setCodeBeforeClass(String codeBeforeClass) { this.codeBeforeClass = codeBeforeClass; } void init2ndPass() { init2ndPassNamesWithDefaults(); for (int i = 0; i < properties.size(); i++) { Property property = properties.get(i); property.setOrdinal(i); property.init2ndPass(); if (property.isPrimaryKey()) { propertiesPk.add(property); } else { propertiesNonPk.add(property); } } for (int i = 0; i < indexes.size(); i++) { final Index index = indexes.get(i); final int propertiesSize = index.getProperties().size(); if (propertiesSize == 1) { final Property property = index.getProperties().get(0); property.setIndex(index); } else if (propertiesSize > 1) { multiIndexes.add(index); } } if (propertiesPk.size() == 1) { pkProperty = propertiesPk.get(0); pkType = schema.mapToJavaTypeNullable(pkProperty.getPropertyType()); } else { pkType = ""Void""; } propertiesColumns = new ArrayList<>(properties); for (ToOne toOne : toOneRelations) { toOne.init2ndPass(); Property[] fkProperties = toOne.getFkProperties(); for (Property fkProperty : fkProperties) { if (!propertiesColumns.contains(fkProperty)) { propertiesColumns.add(fkProperty); } } } for (ToManyBase toMany : toManyRelations) { toMany.init2ndPass(); // Source Properties may not be virtual, so we do not need the following code: // for (Property sourceProperty : toMany.getSourceProperties()) { // if (!propertiesColumns.contains(sourceProperty)) { // propertiesColumns.add(sourceProperty); // } // } } if (active == null) { active = schema.isUseActiveEntitiesByDefault(); } active |= !toOneRelations.isEmpty() || !toManyRelations.isEmpty(); if (hasKeepSections == null) { hasKeepSections = schema.isHasKeepSectionsByDefault(); } init2ndPassIndexNamesWithDefaults(); for (ContentProvider contentProvider : contentProviders) { contentProvider.init2ndPass(); } } protected void init2ndPassNamesWithDefaults() { if (dbName == null) { dbName = DaoUtil.dbName(className); nonDefaultDbName = false; } if (classNameDao == null) { classNameDao = className + ""Dao""; } if (classNameTest == null) { classNameTest = className + ""Test""; } if (javaPackage == null) { javaPackage = schema.getDefaultJavaPackage(); } if (javaPackageDao == null) { javaPackageDao = schema.getDefaultJavaPackageDao(); if (javaPackageDao == null) { javaPackageDao = javaPackage; } } if (javaPackageTest == null) { javaPackageTest = schema.getDefaultJavaPackageTest(); if (javaPackageTest == null) { javaPackageTest = javaPackage; } } } protected void init2ndPassIndexNamesWithDefaults() { for (int i = 0; i < indexes.size(); i++) { Index index = indexes.get(i); if (index.getName() == null) { String indexName = ""IDX_"" + getDbName(); List properties = index.getProperties(); for (int j = 0; j < properties.size(); j++) { Property property = properties.get(j); indexName += ""_"" + property.getDbName(); if (""DESC"".equalsIgnoreCase(index.getPropertiesOrder().get(j))) { indexName += ""_DESC""; } } // TODO can this get too long? how to shorten reliably without depending on the order (i) index.setDefaultName(indexName); } } } void init3rdPass() { for (Property property : properties) { property.init3ndPass(); } init3rdPassRelations(); init3rdPassAdditionalImports(); } private void init3rdPassRelations() { Set toOneNames = new HashSet<>(); for (ToOne toOne : toOneRelations) { toOne.init3ndPass(); if (!toOneNames.add(toOne.getName().toLowerCase())) { throw new RuntimeException(""Duplicate name for "" + toOne); } } Set toManyNames = new HashSet<>(); for (ToManyBase toMany : toManyRelations) { toMany.init3rdPass(); if (toMany instanceof ToMany) { Entity targetEntity = toMany.getTargetEntity(); for (Property targetProperty : ((ToMany) toMany).getTargetProperties()) { if (!targetEntity.propertiesColumns.contains(targetProperty)) { targetEntity.propertiesColumns.add(targetProperty); } } } if (!toManyNames.add(toMany.getName().toLowerCase())) { throw new RuntimeException(""Duplicate name for "" + toMany); } } } private void init3rdPassAdditionalImports() { if (active && !javaPackage.equals(javaPackageDao)) { additionalImportsEntity.add(javaPackageDao + ""."" + classNameDao); } for (ToOne toOne : toOneRelations) { Entity targetEntity = toOne.getTargetEntity(); checkAdditionalImportsEntityTargetEntity(targetEntity); // For deep loading checkAdditionalImportsDaoTargetEntity(targetEntity); } for (ToManyBase toMany : toManyRelations) { Entity targetEntity = toMany.getTargetEntity(); checkAdditionalImportsEntityTargetEntity(targetEntity); } for (ToManyBase incomingToMany : incomingToManyRelations) { if (incomingToMany instanceof ToManyWithJoinEntity) { final ToManyWithJoinEntity toManyWithJoinEntity = (ToManyWithJoinEntity) incomingToMany; final Entity joinEntity = toManyWithJoinEntity.getJoinEntity(); checkAdditionalImportsDaoTargetEntity(joinEntity); } } for (Property property : properties) { String customType = property.getCustomType(); if (customType != null) { String pack = DaoUtil.getPackageFromFullyQualified(customType); if (pack != null && !pack.equals(javaPackage)) { additionalImportsEntity.add(customType); } if (pack != null && !pack.equals(javaPackageDao)) { additionalImportsDao.add(customType); } } String converter = property.getConverter(); if (converter != null) { String pack = DaoUtil.getPackageFromFullyQualified(converter); if (pack != null && !pack.equals(javaPackageDao)) { additionalImportsDao.add(converter); } } } } private void checkAdditionalImportsEntityTargetEntity(Entity targetEntity) { if (!targetEntity.getJavaPackage().equals(javaPackage)) { additionalImportsEntity.add(targetEntity.getJavaPackage() + ""."" + targetEntity.getClassName()); } if (!targetEntity.getJavaPackageDao().equals(javaPackage)) { additionalImportsEntity.add(targetEntity.getJavaPackageDao() + ""."" + targetEntity.getClassNameDao()); } } private void checkAdditionalImportsDaoTargetEntity(Entity targetEntity) { if (!targetEntity.getJavaPackage().equals(javaPackageDao)) { additionalImportsDao.add(targetEntity.getJavaPackage() + ""."" + targetEntity.getClassName()); } } public void validatePropertyExists(Property property) { if (!properties.contains(property)) { throw new RuntimeException(""Property "" + property + "" does not exist in "" + this); } } public List getMultiIndexes() { return multiIndexes; } public boolean isNonDefaultDbName() { return nonDefaultDbName; } @Override public String toString() { return ""Entity "" + className + "" (package: "" + javaPackage + "")""; } } ","classToExtend " "/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.buffer; import io.netty.util.ByteProcessor; import io.netty.util.IllegalReferenceCountException; import io.netty.util.ReferenceCountUtil; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.RecyclableArrayList; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import static io.netty.util.internal.ObjectUtil.checkNotNull; /** * A virtual buffer which shows multiple buffers as a single merged buffer. It is recommended to use * {@link ByteBufAllocator#compositeBuffer()} or {@link Unpooled#wrappedBuffer(ByteBuf...)} instead of calling the * constructor explicitly. */ public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable { private static final ByteBuffer EMPTY_NIO_BUFFER = Unpooled.EMPTY_BUFFER.nioBuffer(); private static final Iterator EMPTY_ITERATOR = Collections.emptyList().iterator(); private final ByteBufAllocator alloc; private final boolean direct; private final int maxNumComponents; private int componentCount; private Component[] components; // resized when needed private boolean freed; private CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, int initSize) { super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY); this.alloc = ObjectUtil.checkNotNull(alloc, ""alloc""); if (maxNumComponents < 1) { throw new IllegalArgumentException( ""maxNumComponents: "" + maxNumComponents + "" (expected: >= 1)""); } this.direct = direct; this.maxNumComponents = maxNumComponents; components = newCompArray(initSize, maxNumComponents); } public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents) { this(alloc, direct, maxNumComponents, 0); } public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf... buffers) { this(alloc, direct, maxNumComponents, buffers, 0); } CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf[] buffers, int offset) { this(alloc, direct, maxNumComponents, buffers.length - offset); addComponents0(false, 0, buffers, offset); consolidateIfNeeded(); setIndex0(0, capacity()); } public CompositeByteBuf( ByteBufAllocator alloc, boolean direct, int maxNumComponents, Iterable buffers) { this(alloc, direct, maxNumComponents, buffers instanceof Collection ? ((Collection) buffers).size() : 0); addComponents(false, 0, buffers); setIndex(0, capacity()); } // support passing arrays of other types instead of having to copy to a ByteBuf[] first interface ByteWrapper { ByteBuf wrap(T bytes); boolean isEmpty(T bytes); } static final ByteWrapper BYTE_ARRAY_WRAPPER = new ByteWrapper() { @Override public ByteBuf wrap(byte[] bytes) { return Unpooled.wrappedBuffer(bytes); } @Override public boolean isEmpty(byte[] bytes) { return bytes.length == 0; } }; static final ByteWrapper BYTE_BUFFER_WRAPPER = new ByteWrapper() { @Override public ByteBuf wrap(ByteBuffer bytes) { return Unpooled.wrappedBuffer(bytes); } @Override public boolean isEmpty(ByteBuffer bytes) { return !bytes.hasRemaining(); } }; CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteWrapper wrapper, T[] buffers, int offset) { this(alloc, direct, maxNumComponents, buffers.length - offset); addComponents0(false, 0, wrapper, buffers, offset); consolidateIfNeeded(); setIndex(0, capacity()); } private static Component[] newCompArray(int initComponents, int maxNumComponents) { int capacityGuess = Math.min(AbstractByteBufAllocator.DEFAULT_MAX_COMPONENTS, maxNumComponents); return new Component[Math.max(initComponents, capacityGuess)]; } // Special constructor used by WrappedCompositeByteBuf CompositeByteBuf(ByteBufAllocator alloc) { super(Integer.MAX_VALUE); this.alloc = alloc; direct = false; maxNumComponents = 0; components = null; } /** * Add the given {@link ByteBuf}. *

* Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. * If you need to have it increased use {@link #addComponent(boolean, ByteBuf)}. *

* {@link ByteBuf#release()} ownership of {@code buffer} is transferred to this {@link CompositeByteBuf}. * @param buffer the {@link ByteBuf} to add. {@link ByteBuf#release()} ownership is transferred to this * {@link CompositeByteBuf}. */ public CompositeByteBuf addComponent(ByteBuf buffer) { return addComponent(false, buffer); } /** * Add the given {@link ByteBuf}s. *

* Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. * If you need to have it increased use {@link #addComponents(boolean, ByteBuf[])}. *

* {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects in {@code buffers} is transferred to this * {@link CompositeByteBuf}. * @param buffers the {@link ByteBuf}s to add. {@link ByteBuf#release()} ownership of all {@link ByteBuf#release()} * ownership of all {@link ByteBuf} objects is transferred to this {@link CompositeByteBuf}. */ public CompositeByteBuf addComponents(ByteBuf... buffers) { return addComponents(false, buffers); } /** * Add the given {@link ByteBuf}s. *

* Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. * If you need to have it increased use {@link #addComponents(boolean, Iterable)}. *

* {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects in {@code buffers} is transferred to this * {@link CompositeByteBuf}. * @param buffers the {@link ByteBuf}s to add. {@link ByteBuf#release()} ownership of all {@link ByteBuf#release()} * ownership of all {@link ByteBuf} objects is transferred to this {@link CompositeByteBuf}. */ public CompositeByteBuf addComponents(Iterable buffers) { return addComponents(false, buffers); } /** * Add the given {@link ByteBuf} on the specific index. *

* Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. * If you need to have it increased use {@link #addComponent(boolean, int, ByteBuf)}. *

* {@link ByteBuf#release()} ownership of {@code buffer} is transferred to this {@link CompositeByteBuf}. * @param cIndex the index on which the {@link ByteBuf} will be added. * @param buffer the {@link ByteBuf} to add. {@link ByteBuf#release()} ownership is transferred to this * {@link CompositeByteBuf}. */ public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) { return addComponent(false, cIndex, buffer); } /** * Add the given {@link ByteBuf} and increase the {@code writerIndex} if {@code increaseWriterIndex} is * {@code true}. * * {@link ByteBuf#release()} ownership of {@code buffer} is transferred to this {@link CompositeByteBuf}. * @param buffer the {@link ByteBuf} to add. {@link ByteBuf#release()} ownership is transferred to this * {@link CompositeByteBuf}. */ public CompositeByteBuf addComponent(boolean increaseWriterIndex, ByteBuf buffer) { return addComponent(increaseWriterIndex, componentCount, buffer); } /** * Add the given {@link ByteBuf}s and increase the {@code writerIndex} if {@code increaseWriterIndex} is * {@code true}. * * {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects in {@code buffers} is transferred to this * {@link CompositeByteBuf}. * @param buffers the {@link ByteBuf}s to add. {@link ByteBuf#release()} ownership of all {@link ByteBuf#release()} * ownership of all {@link ByteBuf} objects is transferred to this {@link CompositeByteBuf}. */ public CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers) { checkNotNull(buffers, ""buffers""); addComponents0(increaseWriterIndex, componentCount, buffers, 0); consolidateIfNeeded(); return this; } /** * Add the given {@link ByteBuf}s and increase the {@code writerIndex} if {@code increaseWriterIndex} is * {@code true}. * * {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects in {@code buffers} is transferred to this * {@link CompositeByteBuf}. * @param buffers the {@link ByteBuf}s to add. {@link ByteBuf#release()} ownership of all {@link ByteBuf#release()} * ownership of all {@link ByteBuf} objects is transferred to this {@link CompositeByteBuf}. */ public CompositeByteBuf addComponents(boolean increaseWriterIndex, Iterable buffers) { return addComponents(increaseWriterIndex, componentCount, buffers); } /** * Add the given {@link ByteBuf} on the specific index and increase the {@code writerIndex} * if {@code increaseWriterIndex} is {@code true}. * * {@link ByteBuf#release()} ownership of {@code buffer} is transferred to this {@link CompositeByteBuf}. * @param cIndex the index on which the {@link ByteBuf} will be added. * @param buffer the {@link ByteBuf} to add. {@link ByteBuf#release()} ownership is transferred to this * {@link CompositeByteBuf}. */ public CompositeByteBuf addComponent(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) { checkNotNull(buffer, ""buffer""); addComponent0(increaseWriterIndex, cIndex, buffer); consolidateIfNeeded(); return this; } private static void checkForOverflow(int capacity, int readableBytes) { if (capacity + readableBytes < 0) { throw new IllegalArgumentException(""Can't increase by "" + readableBytes + "" as capacity("" + capacity + "")"" + "" would overflow "" + Integer.MAX_VALUE); } } /** * Precondition is that {@code buffer != null}. */ private int addComponent0(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) { assert buffer != null; boolean wasAdded = false; try { checkComponentIndex(cIndex); // No need to consolidate - just add a component to the list. Component c = newComponent(ensureAccessible(buffer), 0); int readableBytes = c.length(); // Check if we would overflow. // See https://github.com/netty/netty/issues/10194 checkForOverflow(capacity(), readableBytes); addComp(cIndex, c); wasAdded = true; if (readableBytes > 0 && cIndex < componentCount - 1) { updateComponentOffsets(cIndex); } else if (cIndex > 0) { c.reposition(components[cIndex - 1].endOffset); } if (increaseWriterIndex) { writerIndex += readableBytes; } return cIndex; } finally { if (!wasAdded) { buffer.release(); } } } private static ByteBuf ensureAccessible(final ByteBuf buf) { if (checkAccessible && !buf.isAccessible()) { throw new IllegalReferenceCountException(0); } return buf; } @SuppressWarnings(""deprecation"") private Component newComponent(final ByteBuf buf, final int offset) { final int srcIndex = buf.readerIndex(); final int len = buf.readableBytes(); // unpeel any intermediate outer layers (UnreleasableByteBuf, LeakAwareByteBufs, SwappedByteBuf) ByteBuf unwrapped = buf; int unwrappedIndex = srcIndex; while (unwrapped instanceof WrappedByteBuf || unwrapped instanceof SwappedByteBuf) { unwrapped = unwrapped.unwrap(); } // unwrap if already sliced if (unwrapped instanceof AbstractUnpooledSlicedByteBuf) { unwrappedIndex += ((AbstractUnpooledSlicedByteBuf) unwrapped).idx(0); unwrapped = unwrapped.unwrap(); } else if (unwrapped instanceof PooledSlicedByteBuf) { unwrappedIndex += ((PooledSlicedByteBuf) unwrapped).adjustment; unwrapped = unwrapped.unwrap(); } else if (unwrapped instanceof DuplicatedByteBuf || unwrapped instanceof PooledDuplicatedByteBuf) { unwrapped = unwrapped.unwrap(); } // We don't need to slice later to expose the internal component if the readable range // is already the entire buffer final ByteBuf slice = buf.capacity() == len ? buf : null; return new Component(buf.order(ByteOrder.BIG_ENDIAN), srcIndex, unwrapped.order(ByteOrder.BIG_ENDIAN), unwrappedIndex, offset, len, slice); } /** * Add the given {@link ByteBuf}s on the specific index *

* Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. * If you need to have it increased you need to handle it by your own. *

* {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects in {@code buffers} is transferred to this * {@link CompositeByteBuf}. * @param cIndex the index on which the {@link ByteBuf} will be added. {@link ByteBuf#release()} ownership of all * {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects is transferred to this * {@link CompositeByteBuf}. * @param buffers the {@link ByteBuf}s to add. {@link ByteBuf#release()} ownership of all {@link ByteBuf#release()} * ownership of all {@link ByteBuf} objects is transferred to this {@link CompositeByteBuf}. */ public CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) { checkNotNull(buffers, ""buffers""); addComponents0(false, cIndex, buffers, 0); consolidateIfNeeded(); return this; } private CompositeByteBuf addComponents0(boolean increaseWriterIndex, final int cIndex, ByteBuf[] buffers, int [MASK] ) { final int len = buffers.length, count = len - [MASK] ; int readableBytes = 0; int capacity = capacity(); for (int i = [MASK] ; i < buffers.length; i++) { ByteBuf b = buffers[i]; if (b == null) { break; } readableBytes += b.readableBytes(); // Check if we would overflow. // See https://github.com/netty/netty/issues/10194 checkForOverflow(capacity, readableBytes); } // only set ci after we've shifted so that finally block logic is always correct int ci = Integer.MAX_VALUE; try { checkComponentIndex(cIndex); shiftComps(cIndex, count); // will increase componentCount int nextOffset = cIndex > 0 ? components[cIndex - 1].endOffset : 0; for (ci = cIndex; [MASK] < len; [MASK] ++, ci++) { ByteBuf b = buffers[ [MASK] ]; if (b == null) { break; } Component c = newComponent(ensureAccessible(b), nextOffset); components[ci] = c; nextOffset = c.endOffset; } return this; } finally { // ci is now the index following the last successfully added component if (ci < componentCount) { if (ci < cIndex + count) { // we bailed early removeCompRange(ci, cIndex + count); for (; [MASK] < len; ++ [MASK] ) { ReferenceCountUtil.safeRelease(buffers[ [MASK] ]); } } updateComponentOffsets(ci); // only need to do this here for components after the added ones } if (increaseWriterIndex && ci > cIndex && ci <= componentCount) { writerIndex += components[ci - 1].endOffset - components[cIndex].offset; } } } private int addComponents0(boolean increaseWriterIndex, int cIndex, ByteWrapper wrapper, T[] buffers, int offset) { checkComponentIndex(cIndex); // No need for consolidation for (int i = offset, len = buffers.length; i < len; i++) { T b = buffers[i]; if (b == null) { break; } if (!wrapper.isEmpty(b)) { cIndex = addComponent0(increaseWriterIndex, cIndex, wrapper.wrap(b)) + 1; int size = componentCount; if (cIndex > size) { cIndex = size; } } } return cIndex; } /** * Add the given {@link ByteBuf}s on the specific index * * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}. * If you need to have it increased you need to handle it by your own. *

* {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects in {@code buffers} is transferred to this * {@link CompositeByteBuf}. * @param cIndex the index on which the {@link ByteBuf} will be added. * @param buffers the {@link ByteBuf}s to add. {@link ByteBuf#release()} ownership of all * {@link ByteBuf#release()} ownership of all {@link ByteBuf} objects is transferred to this * {@link CompositeByteBuf}. */ public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { return addComponents(false, cIndex, buffers); } /** * Add the given {@link ByteBuf} and increase the {@code writerIndex} if {@code increaseWriterIndex} is * {@code true}. If the provided buffer is a {@link CompositeByteBuf} itself, a ""shallow copy"" of its * readable components will be performed. Thus the actual number of new components added may vary * and in particular will be zero if the provided buffer is not readable. *

* {@link ByteBuf#release()} ownership of {@code buffer} is transferred to this {@link CompositeByteBuf}. * @param buffer the {@link ByteBuf} to add. {@link ByteBuf#release()} ownership is transferred to this * {@link CompositeByteBuf}. */ public CompositeByteBuf addFlattenedComponents(boolean increaseWriterIndex, ByteBuf buffer) { checkNotNull(buffer, ""buffer""); final int ridx = buffer.readerIndex(); final int widx = buffer.writerIndex(); if (ridx == widx) { buffer.release(); return this; } if (!(buffer instanceof CompositeByteBuf)) { addComponent0(increaseWriterIndex, componentCount, buffer); consolidateIfNeeded(); return this; } final CompositeByteBuf from; if (buffer instanceof WrappedCompositeByteBuf) { from = (CompositeByteBuf) buffer.unwrap(); } else { from = (CompositeByteBuf) buffer; } from.checkIndex(ridx, widx - ridx); final Component[] fromComponents = from.components; final int compCountBefore = componentCount; final int writerIndexBefore = writerIndex; try { for (int cidx = from.toComponentIndex0(ridx), newOffset = capacity();; cidx++) { final Component component = fromComponents[cidx]; final int compOffset = component.offset; final int fromIdx = Math.max(ridx, compOffset); final int toIdx = Math.min(widx, component.endOffset); final int len = toIdx - fromIdx; if (len > 0) { // skip empty components addComp(componentCount, new Component( component.srcBuf.retain(), component.srcIdx(fromIdx), component.buf, component.idx(fromIdx), newOffset, len, null)); } if (widx == toIdx) { break; } newOffset += len; } if (increaseWriterIndex) { writerIndex = writerIndexBefore + (widx - ridx); } consolidateIfNeeded(); buffer.release(); buffer = null; return this; } finally { if (buffer != null) { // if we did not succeed, attempt to rollback any components that were added if (increaseWriterIndex) { writerIndex = writerIndexBefore; } for (int cidx = componentCount - 1; cidx >= compCountBefore; cidx--) { components[cidx].free(); removeComp(cidx); } } } } // TODO optimize further, similar to ByteBuf[] version // (difference here is that we don't know *always* know precise size increase in advance, // but we do in the most common case that the Iterable is a Collection) private CompositeByteBuf addComponents(boolean increaseIndex, int cIndex, Iterable buffers) { if (buffers instanceof ByteBuf) { // If buffers also implements ByteBuf (e.g. CompositeByteBuf), it has to go to addComponent(ByteBuf). return addComponent(increaseIndex, cIndex, (ByteBuf) buffers); } checkNotNull(buffers, ""buffers""); Iterator it = buffers.iterator(); try { checkComponentIndex(cIndex); // No need for consolidation while (it.hasNext()) { ByteBuf b = it.next(); if (b == null) { break; } cIndex = addComponent0(increaseIndex, cIndex, b) + 1; cIndex = Math.min(cIndex, componentCount); } } finally { while (it.hasNext()) { ReferenceCountUtil.safeRelease(it.next()); } } consolidateIfNeeded(); return this; } /** * This should only be called as last operation from a method as this may adjust the underlying * array of components and so affect the index etc. */ private void consolidateIfNeeded() { // Consolidate if the number of components will exceed the allowed maximum by the current // operation. int size = componentCount; if (size > maxNumComponents) { consolidate0(0, size); } } private void checkComponentIndex(int cIndex) { ensureAccessible(); if (cIndex < 0 || cIndex > componentCount) { throw new IndexOutOfBoundsException(String.format( ""cIndex: %d (expected: >= 0 && <= numComponents(%d))"", cIndex, componentCount)); } } private void checkComponentIndex(int cIndex, int numComponents) { ensureAccessible(); if (cIndex < 0 || cIndex + numComponents > componentCount) { throw new IndexOutOfBoundsException(String.format( ""cIndex: %d, numComponents: %d "" + ""(expected: cIndex >= 0 && cIndex + numComponents <= totalNumComponents(%d))"", cIndex, numComponents, componentCount)); } } private void updateComponentOffsets(int cIndex) { int size = componentCount; if (size <= cIndex) { return; } int nextIndex = cIndex > 0 ? components[cIndex - 1].endOffset : 0; for (; cIndex < size; cIndex++) { Component c = components[cIndex]; c.reposition(nextIndex); nextIndex = c.endOffset; } } /** * Remove the {@link ByteBuf} from the given index. * * @param cIndex the index on from which the {@link ByteBuf} will be remove */ public CompositeByteBuf removeComponent(int cIndex) { checkComponentIndex(cIndex); Component comp = components[cIndex]; if (lastAccessed == comp) { lastAccessed = null; } comp.free(); removeComp(cIndex); if (comp.length() > 0) { // Only need to call updateComponentOffsets if the length was > 0 updateComponentOffsets(cIndex); } return this; } /** * Remove the number of {@link ByteBuf}s starting from the given index. * * @param cIndex the index on which the {@link ByteBuf}s will be started to removed * @param numComponents the number of components to remove */ public CompositeByteBuf removeComponents(int cIndex, int numComponents) { checkComponentIndex(cIndex, numComponents); if (numComponents == 0) { return this; } int endIndex = cIndex + numComponents; boolean needsUpdate = false; for (int i = cIndex; i < endIndex; ++i) { Component c = components[i]; if (c.length() > 0) { needsUpdate = true; } if (lastAccessed == c) { lastAccessed = null; } c.free(); } removeCompRange(cIndex, endIndex); if (needsUpdate) { // Only need to call updateComponentOffsets if the length was > 0 updateComponentOffsets(cIndex); } return this; } @Override public Iterator iterator() { ensureAccessible(); return componentCount == 0 ? EMPTY_ITERATOR : new CompositeByteBufIterator(); } @Override protected int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception { if (end <= start) { return -1; } for (int i = toComponentIndex0(start), length = end - start; length > 0; i++) { Component c = components[i]; if (c.offset == c.endOffset) { continue; // empty } ByteBuf s = c.buf; int localStart = c.idx(start); int localLength = Math.min(length, c.endOffset - start); // avoid additional checks in AbstractByteBuf case int result = s instanceof AbstractByteBuf ? ((AbstractByteBuf) s).forEachByteAsc0(localStart, localStart + localLength, processor) : s.forEachByte(localStart, localLength, processor); if (result != -1) { return result - c.adjustment; } start += localLength; length -= localLength; } return -1; } @Override protected int forEachByteDesc0(int rStart, int rEnd, ByteProcessor processor) throws Exception { if (rEnd > rStart) { // rStart *and* rEnd are inclusive return -1; } for (int i = toComponentIndex0(rStart), length = 1 + rStart - rEnd; length > 0; i--) { Component c = components[i]; if (c.offset == c.endOffset) { continue; // empty } ByteBuf s = c.buf; int localRStart = c.idx(length + rEnd); int localLength = Math.min(length, localRStart), localIndex = localRStart - localLength; // avoid additional checks in AbstractByteBuf case int result = s instanceof AbstractByteBuf ? ((AbstractByteBuf) s).forEachByteDesc0(localRStart - 1, localIndex, processor) : s.forEachByteDesc(localIndex, localLength, processor); if (result != -1) { return result - c.adjustment; } length -= localLength; } return -1; } /** * Same with {@link #slice(int, int)} except that this method returns a list. */ public List decompose(int offset, int length) { checkIndex(offset, length); if (length == 0) { return Collections.emptyList(); } int componentId = toComponentIndex0(offset); int bytesToSlice = length; // The first component Component firstC = components[componentId]; // It's important to use srcBuf and NOT buf as we need to return the ""original"" source buffer and not the // unwrapped one as otherwise we could loose the ability to correctly update the reference count on the // returned buffer. ByteBuf slice = firstC.srcBuf.slice(firstC.srcIdx(offset), Math.min(firstC.endOffset - offset, bytesToSlice)); bytesToSlice -= slice.readableBytes(); if (bytesToSlice == 0) { return Collections.singletonList(slice); } List sliceList = new ArrayList(componentCount - componentId); sliceList.add(slice); // Add all the slices until there is nothing more left and then return the List. do { Component component = components[++componentId]; // It's important to use srcBuf and NOT buf as we need to return the ""original"" source buffer and not the // unwrapped one as otherwise we could loose the ability to correctly update the reference count on the // returned buffer. slice = component.srcBuf.slice(component.srcIdx(component.offset), Math.min(component.length(), bytesToSlice)); bytesToSlice -= slice.readableBytes(); sliceList.add(slice); } while (bytesToSlice > 0); return sliceList; } @Override public boolean isDirect() { int size = componentCount; if (size == 0) { return false; } for (int i = 0; i < size; i++) { if (!components[i].buf.isDirect()) { return false; } } return true; } @Override public boolean hasArray() { switch (componentCount) { case 0: return true; case 1: return components[0].buf.hasArray(); default: return false; } } @Override public byte[] array() { switch (componentCount) { case 0: return EmptyArrays.EMPTY_BYTES; case 1: return components[0].buf.array(); default: throw new UnsupportedOperationException(); } } @Override public int arrayOffset() { switch (componentCount) { case 0: return 0; case 1: Component c = components[0]; return c.idx(c.buf.arrayOffset()); default: throw new UnsupportedOperationException(); } } @Override public boolean hasMemoryAddress() { switch (componentCount) { case 0: return Unpooled.EMPTY_BUFFER.hasMemoryAddress(); case 1: return components[0].buf.hasMemoryAddress(); default: return false; } } @Override public long memoryAddress() { switch (componentCount) { case 0: return Unpooled.EMPTY_BUFFER.memoryAddress(); case 1: Component c = components[0]; return c.buf.memoryAddress() + c.adjustment; default: throw new UnsupportedOperationException(); } } @Override public int capacity() { int size = componentCount; return size > 0 ? components[size - 1].endOffset : 0; } @Override public CompositeByteBuf capacity(int newCapacity) { checkNewCapacity(newCapacity); final int size = componentCount, oldCapacity = capacity(); if (newCapacity > oldCapacity) { final int paddingLength = newCapacity - oldCapacity; ByteBuf padding = allocBuffer(paddingLength).setIndex(0, paddingLength); addComponent0(false, size, padding); if (componentCount >= maxNumComponents) { // FIXME: No need to create a padding buffer and consolidate. // Just create a big single buffer and put the current content there. consolidateIfNeeded(); } } else if (newCapacity < oldCapacity) { lastAccessed = null; int i = size - 1; for (int bytesToTrim = oldCapacity - newCapacity; i >= 0; i--) { Component c = components[i]; final int cLength = c.length(); if (bytesToTrim < cLength) { // Trim the last component c.endOffset -= bytesToTrim; ByteBuf slice = c.slice; if (slice != null) { // We must replace the cached slice with a derived one to ensure that // it can later be released properly in the case of PooledSlicedByteBuf. c.slice = slice.slice(0, c.length()); } break; } c.free(); bytesToTrim -= cLength; } removeCompRange(i + 1, size); if (readerIndex() > newCapacity) { setIndex0(newCapacity, newCapacity); } else if (writerIndex > newCapacity) { writerIndex = newCapacity; } } return this; } @Override public ByteBufAllocator alloc() { return alloc; } @Override public ByteOrder order() { return ByteOrder.BIG_ENDIAN; } /** * Return the current number of {@link ByteBuf}'s that are composed in this instance */ public int numComponents() { return componentCount; } /** * Return the max number of {@link ByteBuf}'s that are composed in this instance */ public int maxNumComponents() { return maxNumComponents; } /** * Return the index for the given offset */ public int toComponentIndex(int offset) { checkIndex(offset); return toComponentIndex0(offset); } private int toComponentIndex0(int offset) { int size = componentCount; if (offset == 0) { // fast-path zero offset for (int i = 0; i < size; i++) { if (components[i].endOffset > 0) { return i; } } } if (size <= 2) { // fast-path for 1 and 2 component count return size == 1 || offset < components[0].endOffset ? 0 : 1; } for (int low = 0, high = size; low <= high;) { int mid = low + high >>> 1; Component c = components[mid]; if (offset >= c.endOffset) { low = mid + 1; } else if (offset < c.offset) { high = mid - 1; } else { return mid; } } throw new Error(""should not reach here""); } public int toByteIndex(int cIndex) { checkComponentIndex(cIndex); return components[cIndex].offset; } @Override public byte getByte(int index) { Component c = findComponent(index); return c.buf.getByte(c.idx(index)); } @Override protected byte _getByte(int index) { Component c = findComponent0(index); return c.buf.getByte(c.idx(index)); } @Override protected short _getShort(int index) { Component c = findComponent0(index); if (index + 2 <= c.endOffset) { return c.buf.getShort(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff); } else { return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8); } } @Override protected short _getShortLE(int index) { Component c = findComponent0(index); if (index + 2 <= c.endOffset) { return c.buf.getShortLE(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8); } else { return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff); } } @Override protected int _getUnsignedMedium(int index) { Component c = findComponent0(index); if (index + 3 <= c.endOffset) { return c.buf.getUnsignedMedium(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return (_getShort(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff; } else { return _getShort(index) & 0xFFFF | (_getByte(index + 2) & 0xFF) << 16; } } @Override protected int _getUnsignedMediumLE(int index) { Component c = findComponent0(index); if (index + 3 <= c.endOffset) { return c.buf.getUnsignedMediumLE(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return _getShortLE(index) & 0xffff | (_getByte(index + 2) & 0xff) << 16; } else { return (_getShortLE(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff; } } @Override protected int _getInt(int index) { Component c = findComponent0(index); if (index + 4 <= c.endOffset) { return c.buf.getInt(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return (_getShort(index) & 0xffff) << 16 | _getShort(index + 2) & 0xffff; } else { return _getShort(index) & 0xFFFF | (_getShort(index + 2) & 0xFFFF) << 16; } } @Override protected int _getIntLE(int index) { Component c = findComponent0(index); if (index + 4 <= c.endOffset) { return c.buf.getIntLE(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return _getShortLE(index) & 0xffff | (_getShortLE(index + 2) & 0xffff) << 16; } else { return (_getShortLE(index) & 0xffff) << 16 | _getShortLE(index + 2) & 0xffff; } } @Override protected long _getLong(int index) { Component c = findComponent0(index); if (index + 8 <= c.endOffset) { return c.buf.getLong(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return (_getInt(index) & 0xffffffffL) << 32 | _getInt(index + 4) & 0xffffffffL; } else { return _getInt(index) & 0xFFFFFFFFL | (_getInt(index + 4) & 0xFFFFFFFFL) << 32; } } @Override protected long _getLongLE(int index) { Component c = findComponent0(index); if (index + 8 <= c.endOffset) { return c.buf.getLongLE(c.idx(index)); } else if (order() == ByteOrder.BIG_ENDIAN) { return _getIntLE(index) & 0xffffffffL | (_getIntLE(index + 4) & 0xffffffffL) << 32; } else { return (_getIntLE(index) & 0xffffffffL) << 32 | _getIntLE(index + 4) & 0xffffffffL; } } @Override public CompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { checkDstIndex(index, length, dstIndex, dst.length); if (length == 0) { return this; } int i = toComponentIndex0(index); while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); c.buf.getBytes(c.idx(index), dst, dstIndex, localLength); index += localLength; dstIndex += localLength; length -= localLength; i ++; } return this; } @Override public CompositeByteBuf getBytes(int index, ByteBuffer dst) { int limit = dst.limit(); int length = dst.remaining(); checkIndex(index, length); if (length == 0) { return this; } int i = toComponentIndex0(index); try { while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); dst.limit(dst.position() + localLength); c.buf.getBytes(c.idx(index), dst); index += localLength; length -= localLength; i ++; } } finally { dst.limit(limit); } return this; } @Override public CompositeByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { checkDstIndex(index, length, dstIndex, dst.capacity()); if (length == 0) { return this; } int i = toComponentIndex0(index); while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); c.buf.getBytes(c.idx(index), dst, dstIndex, localLength); index += localLength; dstIndex += localLength; length -= localLength; i ++; } return this; } @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { int count = nioBufferCount(); if (count == 1) { return out.write(internalNioBuffer(index, length)); } else { long writtenBytes = out.write(nioBuffers(index, length)); if (writtenBytes > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } else { return (int) writtenBytes; } } } @Override public int getBytes(int index, FileChannel out, long position, int length) throws IOException { int count = nioBufferCount(); if (count == 1) { return out.write(internalNioBuffer(index, length), position); } else { long writtenBytes = 0; for (ByteBuffer buf : nioBuffers(index, length)) { writtenBytes += out.write(buf, position + writtenBytes); } if (writtenBytes > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } return (int) writtenBytes; } } @Override public CompositeByteBuf getBytes(int index, OutputStream out, int length) throws IOException { checkIndex(index, length); if (length == 0) { return this; } int i = toComponentIndex0(index); while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); c.buf.getBytes(c.idx(index), out, localLength); index += localLength; length -= localLength; i ++; } return this; } @Override public CompositeByteBuf setByte(int index, int value) { Component c = findComponent(index); c.buf.setByte(c.idx(index), value); return this; } @Override protected void _setByte(int index, int value) { Component c = findComponent0(index); c.buf.setByte(c.idx(index), value); } @Override public CompositeByteBuf setShort(int index, int value) { checkIndex(index, 2); _setShort(index, value); return this; } @Override protected void _setShort(int index, int value) { Component c = findComponent0(index); if (index + 2 <= c.endOffset) { c.buf.setShort(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setByte(index, (byte) (value >>> 8)); _setByte(index + 1, (byte) value); } else { _setByte(index, (byte) value); _setByte(index + 1, (byte) (value >>> 8)); } } @Override protected void _setShortLE(int index, int value) { Component c = findComponent0(index); if (index + 2 <= c.endOffset) { c.buf.setShortLE(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setByte(index, (byte) value); _setByte(index + 1, (byte) (value >>> 8)); } else { _setByte(index, (byte) (value >>> 8)); _setByte(index + 1, (byte) value); } } @Override public CompositeByteBuf setMedium(int index, int value) { checkIndex(index, 3); _setMedium(index, value); return this; } @Override protected void _setMedium(int index, int value) { Component c = findComponent0(index); if (index + 3 <= c.endOffset) { c.buf.setMedium(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setShort(index, (short) (value >> 8)); _setByte(index + 2, (byte) value); } else { _setShort(index, (short) value); _setByte(index + 2, (byte) (value >>> 16)); } } @Override protected void _setMediumLE(int index, int value) { Component c = findComponent0(index); if (index + 3 <= c.endOffset) { c.buf.setMediumLE(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setShortLE(index, (short) value); _setByte(index + 2, (byte) (value >>> 16)); } else { _setShortLE(index, (short) (value >> 8)); _setByte(index + 2, (byte) value); } } @Override public CompositeByteBuf setInt(int index, int value) { checkIndex(index, 4); _setInt(index, value); return this; } @Override protected void _setInt(int index, int value) { Component c = findComponent0(index); if (index + 4 <= c.endOffset) { c.buf.setInt(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setShort(index, (short) (value >>> 16)); _setShort(index + 2, (short) value); } else { _setShort(index, (short) value); _setShort(index + 2, (short) (value >>> 16)); } } @Override protected void _setIntLE(int index, int value) { Component c = findComponent0(index); if (index + 4 <= c.endOffset) { c.buf.setIntLE(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setShortLE(index, (short) value); _setShortLE(index + 2, (short) (value >>> 16)); } else { _setShortLE(index, (short) (value >>> 16)); _setShortLE(index + 2, (short) value); } } @Override public CompositeByteBuf setLong(int index, long value) { checkIndex(index, 8); _setLong(index, value); return this; } @Override protected void _setLong(int index, long value) { Component c = findComponent0(index); if (index + 8 <= c.endOffset) { c.buf.setLong(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setInt(index, (int) (value >>> 32)); _setInt(index + 4, (int) value); } else { _setInt(index, (int) value); _setInt(index + 4, (int) (value >>> 32)); } } @Override protected void _setLongLE(int index, long value) { Component c = findComponent0(index); if (index + 8 <= c.endOffset) { c.buf.setLongLE(c.idx(index), value); } else if (order() == ByteOrder.BIG_ENDIAN) { _setIntLE(index, (int) value); _setIntLE(index + 4, (int) (value >>> 32)); } else { _setIntLE(index, (int) (value >>> 32)); _setIntLE(index + 4, (int) value); } } @Override public CompositeByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { checkSrcIndex(index, length, srcIndex, src.length); if (length == 0) { return this; } int i = toComponentIndex0(index); while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); c.buf.setBytes(c.idx(index), src, srcIndex, localLength); index += localLength; srcIndex += localLength; length -= localLength; i ++; } return this; } @Override public CompositeByteBuf setBytes(int index, ByteBuffer src) { int limit = src.limit(); int length = src.remaining(); checkIndex(index, length); if (length == 0) { return this; } int i = toComponentIndex0(index); try { while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); src.limit(src.position() + localLength); c.buf.setBytes(c.idx(index), src); index += localLength; length -= localLength; i ++; } } finally { src.limit(limit); } return this; } @Override public CompositeByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { checkSrcIndex(index, length, srcIndex, src.capacity()); if (length == 0) { return this; } int i = toComponentIndex0(index); while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); c.buf.setBytes(c.idx(index), src, srcIndex, localLength); index += localLength; srcIndex += localLength; length -= localLength; i ++; } return this; } @Override public int setBytes(int index, InputStream in, int length) throws IOException { checkIndex(index, length); if (length == 0) { return in.read(EmptyArrays.EMPTY_BYTES); } int i = toComponentIndex0(index); int readBytes = 0; do { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); if (localLength == 0) { // Skip empty buffer i++; continue; } int localReadBytes = c.buf.setBytes(c.idx(index), in, localLength); if (localReadBytes < 0) { if (readBytes == 0) { return -1; } else { break; } } index += localReadBytes; length -= localReadBytes; readBytes += localReadBytes; if (localReadBytes == localLength) { i ++; } } while (length > 0); return readBytes; } @Override public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { checkIndex(index, length); if (length == 0) { return in.read(EMPTY_NIO_BUFFER); } int i = toComponentIndex0(index); int readBytes = 0; do { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); if (localLength == 0) { // Skip empty buffer i++; continue; } int localReadBytes = c.buf.setBytes(c.idx(index), in, localLength); if (localReadBytes == 0) { break; } if (localReadBytes < 0) { if (readBytes == 0) { return -1; } else { break; } } index += localReadBytes; length -= localReadBytes; readBytes += localReadBytes; if (localReadBytes == localLength) { i ++; } } while (length > 0); return readBytes; } @Override public int setBytes(int index, FileChannel in, long position, int length) throws IOException { checkIndex(index, length); if (length == 0) { return in.read(EMPTY_NIO_BUFFER, position); } int i = toComponentIndex0(index); int readBytes = 0; do { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); if (localLength == 0) { // Skip empty buffer i++; continue; } int localReadBytes = c.buf.setBytes(c.idx(index), in, position + readBytes, localLength); if (localReadBytes == 0) { break; } if (localReadBytes < 0) { if (readBytes == 0) { return -1; } else { break; } } index += localReadBytes; length -= localReadBytes; readBytes += localReadBytes; if (localReadBytes == localLength) { i ++; } } while (length > 0); return readBytes; } @Override public ByteBuf copy(int index, int length) { checkIndex(index, length); ByteBuf dst = allocBuffer(length); if (length != 0) { copyTo(index, length, toComponentIndex0(index), dst); } return dst; } private void copyTo(int index, int length, int componentId, ByteBuf dst) { int dstIndex = 0; int i = componentId; while (length > 0) { Component c = components[i]; int localLength = Math.min(length, c.endOffset - index); c.buf.getBytes(c.idx(index), dst, dstIndex, localLength); index += localLength; dstIndex += localLength; length -= localLength; i ++; } dst.writerIndex(dst.capacity()); } /** * Return the {@link ByteBuf} on the specified index * * @param cIndex the index for which the {@link ByteBuf} should be returned * @return buf the {@link ByteBuf} on the specified index */ public ByteBuf component(int cIndex) { checkComponentIndex(cIndex); return components[cIndex].duplicate(); } /** * Return the {@link ByteBuf} on the specified index * * @param offset the offset for which the {@link ByteBuf} should be returned * @return the {@link ByteBuf} on the specified index */ public ByteBuf componentAtOffset(int offset) { return findComponent(offset).duplicate(); } /** * Return the internal {@link ByteBuf} on the specified index. Note that updating the indexes of the returned * buffer will lead to an undefined behavior of this buffer. * * @param cIndex the index for which the {@link ByteBuf} should be returned */ public ByteBuf internalComponent(int cIndex) { checkComponentIndex(cIndex); return components[cIndex].slice(); } /** * Return the internal {@link ByteBuf} on the specified offset. Note that updating the indexes of the returned * buffer will lead to an undefined behavior of this buffer. * * @param offset the offset for which the {@link ByteBuf} should be returned */ public ByteBuf internalComponentAtOffset(int offset) { return findComponent(offset).slice(); } // weak cache - check it first when looking for component private Component lastAccessed; private Component findComponent(int offset) { Component la = lastAccessed; if (la != null && offset >= la.offset && offset < la.endOffset) { ensureAccessible(); return la; } checkIndex(offset); return findIt(offset); } private Component findComponent0(int offset) { Component la = lastAccessed; if (la != null && offset >= la.offset && offset < la.endOffset) { return la; } return findIt(offset); } private Component findIt(int offset) { for (int low = 0, high = componentCount; low <= high;) { int mid = low + high >>> 1; Component c = components[mid]; if (c == null) { throw new IllegalStateException(""No component found for offset. "" + ""Composite buffer layout might be outdated, e.g. from a discardReadBytes call.""); } if (offset >= c.endOffset) { low = mid + 1; } else if (offset < c.offset) { high = mid - 1; } else { lastAccessed = c; return c; } } throw new Error(""should not reach here""); } @Override public int nioBufferCount() { int size = componentCount; switch (size) { case 0: return 1; case 1: return components[0].buf.nioBufferCount(); default: int count = 0; for (int i = 0; i < size; i++) { count += components[i].buf.nioBufferCount(); } return count; } } @Override public ByteBuffer internalNioBuffer(int index, int length) { switch (componentCount) { case 0: return EMPTY_NIO_BUFFER; case 1: return components[0].internalNioBuffer(index, length); default: throw new UnsupportedOperationException(); } } @Override public ByteBuffer nioBuffer(int index, int length) { checkIndex(index, length); switch (componentCount) { case 0: return EMPTY_NIO_BUFFER; case 1: Component c = components[0]; ByteBuf buf = c.buf; if (buf.nioBufferCount() == 1) { return buf.nioBuffer(c.idx(index), length); } break; default: break; } ByteBuffer[] buffers = nioBuffers(index, length); if (buffers.length == 1) { return buffers[0]; } ByteBuffer merged = ByteBuffer.allocate(length).order(order()); for (ByteBuffer buf: buffers) { merged.put(buf); } merged.flip(); return merged; } @Override public ByteBuffer[] nioBuffers(int index, int length) { checkIndex(index, length); if (length == 0) { return new ByteBuffer[] { EMPTY_NIO_BUFFER }; } RecyclableArrayList buffers = RecyclableArrayList.newInstance(componentCount); try { int i = toComponentIndex0(index); while (length > 0) { Component c = components[i]; ByteBuf s = c.buf; int localLength = Math.min(length, c.endOffset - index); switch (s.nioBufferCount()) { case 0: throw new UnsupportedOperationException(); case 1: buffers.add(s.nioBuffer(c.idx(index), localLength)); break; default: Collections.addAll(buffers, s.nioBuffers(c.idx(index), localLength)); } index += localLength; length -= localLength; i ++; } return buffers.toArray(EmptyArrays.EMPTY_BYTE_BUFFERS); } finally { buffers.recycle(); } } /** * Consolidate the composed {@link ByteBuf}s */ public CompositeByteBuf consolidate() { ensureAccessible(); consolidate0(0, componentCount); return this; } /** * Consolidate the composed {@link ByteBuf}s * * @param cIndex the index on which to start to compose * @param numComponents the number of components to compose */ public CompositeByteBuf consolidate(int cIndex, int numComponents) { checkComponentIndex(cIndex, numComponents); consolidate0(cIndex, numComponents); return this; } private void consolidate0(int cIndex, int numComponents) { if (numComponents <= 1) { return; } final int endCIndex = cIndex + numComponents; final int startOffset = cIndex != 0 ? components[cIndex].offset : 0; final int capacity = components[endCIndex - 1].endOffset - startOffset; final ByteBuf consolidated = allocBuffer(capacity); for (int i = cIndex; i < endCIndex; i ++) { components[i].transferTo(consolidated); } lastAccessed = null; removeCompRange(cIndex + 1, endCIndex); components[cIndex] = newComponent(consolidated, 0); if (cIndex != 0 || numComponents != componentCount) { updateComponentOffsets(cIndex); } } /** * Discard all {@link ByteBuf}s which are read. */ public CompositeByteBuf discardReadComponents() { ensureAccessible(); final int readerIndex = readerIndex(); if (readerIndex == 0) { return this; } // Discard everything if (readerIndex = writerIndex = capacity). int writerIndex = writerIndex(); if (readerIndex == writerIndex && writerIndex == capacity()) { for (int i = 0, size = componentCount; i < size; i++) { components[i].free(); } lastAccessed = null; clearComps(); setIndex(0, 0); adjustMarkers(readerIndex); return this; } // Remove read components. int firstComponentId = 0; Component c = null; for (int size = componentCount; firstComponentId < size; firstComponentId++) { c = components[firstComponentId]; if (c.endOffset > readerIndex) { break; } c.free(); } if (firstComponentId == 0) { return this; // Nothing to discard } Component la = lastAccessed; if (la != null && la.endOffset <= readerIndex) { lastAccessed = null; } removeCompRange(0, firstComponentId); // Update indexes and markers. int offset = c.offset; updateComponentOffsets(0); setIndex(readerIndex - offset, writerIndex - offset); adjustMarkers(offset); return this; } @Override public CompositeByteBuf discardReadBytes() { ensureAccessible(); final int readerIndex = readerIndex(); if (readerIndex == 0) { return this; } // Discard everything if (readerIndex = writerIndex = capacity). int writerIndex = writerIndex(); if (readerIndex == writerIndex && writerIndex == capacity()) { for (int i = 0, size = componentCount; i < size; i++) { components[i].free(); } lastAccessed = null; clearComps(); setIndex(0, 0); adjustMarkers(readerIndex); return this; } int firstComponentId = 0; Component c = null; for (int size = componentCount; firstComponentId < size; firstComponentId++) { c = components[firstComponentId]; if (c.endOffset > readerIndex) { break; } c.free(); } // Replace the first readable component with a new slice. int trimmedBytes = readerIndex - c.offset; c.offset = 0; c.endOffset -= readerIndex; c.srcAdjustment += readerIndex; c.adjustment += readerIndex; ByteBuf slice = c.slice; if (slice != null) { // We must replace the cached slice with a derived one to ensure that // it can later be released properly in the case of PooledSlicedByteBuf. c.slice = slice.slice(trimmedBytes, c.length()); } Component la = lastAccessed; if (la != null && la.endOffset <= readerIndex) { lastAccessed = null; } removeCompRange(0, firstComponentId); // Update indexes and markers. updateComponentOffsets(0); setIndex(0, writerIndex - readerIndex); adjustMarkers(readerIndex); return this; } private ByteBuf allocBuffer(int capacity) { return direct ? alloc().directBuffer(capacity) : alloc().heapBuffer(capacity); } @Override public String toString() { String result = super.toString(); result = result.substring(0, result.length() - 1); return result + "", components="" + componentCount + ')'; } private static final class Component { final ByteBuf srcBuf; // the originally added buffer final ByteBuf buf; // srcBuf unwrapped zero or more times int srcAdjustment; // index of the start of this CompositeByteBuf relative to srcBuf int adjustment; // index of the start of this CompositeByteBuf relative to buf int offset; // offset of this component within this CompositeByteBuf int endOffset; // end offset of this component within this CompositeByteBuf private ByteBuf slice; // cached slice, may be null Component(ByteBuf srcBuf, int srcOffset, ByteBuf buf, int bufOffset, int offset, int len, ByteBuf slice) { this.srcBuf = srcBuf; this.srcAdjustment = srcOffset - offset; this.buf = buf; this.adjustment = bufOffset - offset; this.offset = offset; this.endOffset = offset + len; this.slice = slice; } int srcIdx(int index) { return index + srcAdjustment; } int idx(int index) { return index + adjustment; } int length() { return endOffset - offset; } void reposition(int newOffset) { int move = newOffset - offset; endOffset += move; srcAdjustment -= move; adjustment -= move; offset = newOffset; } // copy then release void transferTo(ByteBuf dst) { dst.writeBytes(buf, idx(offset), length()); free(); } ByteBuf slice() { ByteBuf s = slice; if (s == null) { slice = s = srcBuf.slice(srcIdx(offset), length()); } return s; } ByteBuf duplicate() { return srcBuf.duplicate(); } ByteBuffer internalNioBuffer(int index, int length) { // Some buffers override this so we must use srcBuf return srcBuf.internalNioBuffer(srcIdx(index), length); } void free() { slice = null; // Release the original buffer since it may have a different // refcount to the unwrapped buf (e.g. if PooledSlicedByteBuf) srcBuf.release(); } } @Override public CompositeByteBuf readerIndex(int readerIndex) { super.readerIndex(readerIndex); return this; } @Override public CompositeByteBuf writerIndex(int writerIndex) { super.writerIndex(writerIndex); return this; } @Override public CompositeByteBuf setIndex(int readerIndex, int writerIndex) { super.setIndex(readerIndex, writerIndex); return this; } @Override public CompositeByteBuf clear() { super.clear(); return this; } @Override public CompositeByteBuf markReaderIndex() { super.markReaderIndex(); return this; } @Override public CompositeByteBuf resetReaderIndex() { super.resetReaderIndex(); return this; } @Override public CompositeByteBuf markWriterIndex() { super.markWriterIndex(); return this; } @Override public CompositeByteBuf resetWriterIndex() { super.resetWriterIndex(); return this; } @Override public CompositeByteBuf ensureWritable(int minWritableBytes) { super.ensureWritable(minWritableBytes); return this; } @Override public CompositeByteBuf getBytes(int index, ByteBuf dst) { return getBytes(index, dst, dst.writableBytes()); } @Override public CompositeByteBuf getBytes(int index, ByteBuf dst, int length) { getBytes(index, dst, dst.writerIndex(), length); dst.writerIndex(dst.writerIndex() + length); return this; } @Override public CompositeByteBuf getBytes(int index, byte[] dst) { return getBytes(index, dst, 0, dst.length); } @Override public CompositeByteBuf setBoolean(int index, boolean value) { return setByte(index, value? 1 : 0); } @Override public CompositeByteBuf setChar(int index, int value) { return setShort(index, value); } @Override public CompositeByteBuf setFloat(int index, float value) { return setInt(index, Float.floatToRawIntBits(value)); } @Override public CompositeByteBuf setDouble(int index, double value) { return setLong(index, Double.doubleToRawLongBits(value)); } @Override public CompositeByteBuf setBytes(int index, ByteBuf src) { super.setBytes(index, src, src.readableBytes()); return this; } @Override public CompositeByteBuf setBytes(int index, ByteBuf src, int length) { super.setBytes(index, src, length); return this; } @Override public CompositeByteBuf setBytes(int index, byte[] src) { return setBytes(index, src, 0, src.length); } @Override public CompositeByteBuf setZero(int index, int length) { super.setZero(index, length); return this; } @Override public CompositeByteBuf readBytes(ByteBuf dst) { super.readBytes(dst, dst.writableBytes()); return this; } @Override public CompositeByteBuf readBytes(ByteBuf dst, int length) { super.readBytes(dst, length); return this; } @Override public CompositeByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { super.readBytes(dst, dstIndex, length); return this; } @Override public CompositeByteBuf readBytes(byte[] dst) { super.readBytes(dst, 0, dst.length); return this; } @Override public CompositeByteBuf readBytes(byte[] dst, int dstIndex, int length) { super.readBytes(dst, dstIndex, length); return this; } @Override public CompositeByteBuf readBytes(ByteBuffer dst) { super.readBytes(dst); return this; } @Override public CompositeByteBuf readBytes(OutputStream out, int length) throws IOException { super.readBytes(out, length); return this; } @Override public CompositeByteBuf skipBytes(int length) { super.skipBytes(length); return this; } @Override public CompositeByteBuf writeBoolean(boolean value) { writeByte(value ? 1 : 0); return this; } @Override public CompositeByteBuf writeByte(int value) { ensureWritable0(1); _setByte(writerIndex++, value); return this; } @Override public CompositeByteBuf writeShort(int value) { super.writeShort(value); return this; } @Override public CompositeByteBuf writeMedium(int value) { super.writeMedium(value); return this; } @Override public CompositeByteBuf writeInt(int value) { super.writeInt(value); return this; } @Override public CompositeByteBuf writeLong(long value) { super.writeLong(value); return this; } @Override public CompositeByteBuf writeChar(int value) { super.writeShort(value); return this; } @Override public CompositeByteBuf writeFloat(float value) { super.writeInt(Float.floatToRawIntBits(value)); return this; } @Override public CompositeByteBuf writeDouble(double value) { super.writeLong(Double.doubleToRawLongBits(value)); return this; } @Override public CompositeByteBuf writeBytes(ByteBuf src) { super.writeBytes(src, src.readableBytes()); return this; } @Override public CompositeByteBuf writeBytes(ByteBuf src, int length) { super.writeBytes(src, length); return this; } @Override public CompositeByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { super.writeBytes(src, srcIndex, length); return this; } @Override public CompositeByteBuf writeBytes(byte[] src) { super.writeBytes(src, 0, src.length); return this; } @Override public CompositeByteBuf writeBytes(byte[] src, int srcIndex, int length) { super.writeBytes(src, srcIndex, length); return this; } @Override public CompositeByteBuf writeBytes(ByteBuffer src) { super.writeBytes(src); return this; } @Override public CompositeByteBuf writeZero(int length) { super.writeZero(length); return this; } @Override public CompositeByteBuf retain(int increment) { super.retain(increment); return this; } @Override public CompositeByteBuf retain() { super.retain(); return this; } @Override public CompositeByteBuf touch() { return this; } @Override public CompositeByteBuf touch(Object hint) { return this; } @Override public ByteBuffer[] nioBuffers() { return nioBuffers(readerIndex(), readableBytes()); } @Override public CompositeByteBuf discardSomeReadBytes() { return discardReadComponents(); } @Override protected void deallocate() { if (freed) { return; } freed = true; // We're not using foreach to avoid creating an iterator. // see https://github.com/netty/netty/issues/2642 for (int i = 0, size = componentCount; i < size; i++) { components[i].free(); } } @Override boolean isAccessible() { return !freed; } @Override public ByteBuf unwrap() { return null; } private final class CompositeByteBufIterator implements Iterator { private final int size = numComponents(); private int index; @Override public boolean hasNext() { return size > index; } @Override public ByteBuf next() { if (size != numComponents()) { throw new ConcurrentModificationException(); } if (!hasNext()) { throw new NoSuchElementException(); } try { return components[index++].slice(); } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } @Override public void remove() { throw new UnsupportedOperationException(""Read-Only""); } } // Component array manipulation - range checking omitted private void clearComps() { removeCompRange(0, componentCount); } private void removeComp(int i) { removeCompRange(i, i + 1); } private void removeCompRange(int from, int to) { if (from >= to) { return; } final int size = componentCount; assert from >= 0 && to <= size; if (to < size) { System.arraycopy(components, to, components, from, size - to); } int newSize = size - to + from; for (int i = newSize; i < size; i++) { components[i] = null; } componentCount = newSize; } private void addComp(int i, Component c) { shiftComps(i, 1); components[i] = c; } private void shiftComps(int i, int count) { final int size = componentCount, newSize = size + count; assert i >= 0 && i <= size && count > 0; if (newSize > components.length) { // grow the array int newArrSize = Math.max(size + (size >> 1), newSize); Component[] newArr; if (i == size) { newArr = Arrays.copyOf(components, newArrSize, Component[].class); } else { newArr = new Component[newArrSize]; if (i > 0) { System.arraycopy(components, 0, newArr, 0, i); } if (i < size) { System.arraycopy(components, i, newArr, i + count, size - i); } } components = newArr; } else if (i < size) { System.arraycopy(components, i, components, i + count, size - i); } componentCount = newSize; } } ","arrOffset " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 1996-2016, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ package android.icu.text; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.text.FieldPosition; import java.text.Format; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.MissingResourceException; import java.util.UUID; import android.icu.impl.DateNumberFormat; import android.icu.impl.DayPeriodRules; import android.icu.impl.ICUCache; import android.icu.impl.ICUData; import android.icu.impl.ICUResourceBundle; import android.icu.impl.PatternProps; import android.icu.impl.SimpleCache; import android.icu.impl.SimpleFormatterImpl; import android.icu.lang.UCharacter; import android.icu.text.TimeZoneFormat.Style; import android.icu.text.TimeZoneFormat.TimeType; import android.icu.util.BasicTimeZone; import android.icu.util.Calendar; import android.icu.util.HebrewCalendar; import android.icu.util.Output; import android.icu.util.TimeZone; import android.icu.util.TimeZoneTransition; import android.icu.util.ULocale; import android.icu.util.ULocale.Category; import android.icu.util.UResourceBundle; /** * [icu enhancement] ICU's replacement for {@link java.text.SimpleDateFormat}. Methods, fields, and other functionality specific to ICU are labeled '[icu]'. * *

SimpleDateFormat is a concrete class for formatting and * parsing dates in a locale-sensitive manner. It allows for formatting * (date -> text), parsing (text -> date), and normalization. * *

* SimpleDateFormat allows you to start by choosing * any user-defined patterns for date-time formatting. However, you * are encouraged to create a date-time formatter with either * getTimeInstance, getDateInstance, or * getDateTimeInstance in DateFormat. Each * of these class methods can return a date/time formatter initialized * with a default format pattern. You may modify the format pattern * using the applyPattern methods as desired. * For more information on using these methods, see * {@link DateFormat}. * *

Date and Time Patterns:

* *

Date and time formats are specified by date and time pattern strings. * Within date and time pattern strings, all unquoted ASCII letters [A-Za-z] are reserved * as pattern letters representing calendar fields. SimpleDateFormat supports * the date and time formatting algorithm and pattern letters defined by UTS#35 * Unicode Locale Data Markup Language (LDML). The following pattern letters are * currently available (note that the actual values depend on CLDR and may change from the * examples shown here):

*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
FieldSym.No.ExampleDescription
eraG1..3ADEra - Replaced with the Era string for the current date. One to three letters for the * abbreviated form, four letters for the long (wide) form, five for the narrow form.
4Anno Domini
5A
yeary1..n1996Year. Normally the length specifies the padding, but for two letters it also specifies the maximum * length. Example:
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Yearyyyyyyyyyyyyyyy
AD 1101001000100001
AD 121212012001200012
AD 12312323123012300123
AD 12341234341234123401234
AD 123451234545123451234512345
*
*
Y1..n1997Year (in ""Week of Year"" based calendars). Normally the length specifies the padding, * but for two letters it also specifies the maximum length. This year designation is used in ISO * year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems * where week date processing is desired. May not always be the same value as calendar year.
u1..n4601Extended year. This is a single number designating the year of this calendar system, encompassing * all supra-year fields. For example, for the Julian calendar system, year numbers are positive, with an * era of BCE or CE. An extended year value for the Julian calendar system assigns positive values to CE * years and negative values to BCE years, with 1 BCE being year 0.
U1..3甲子Cyclic year name. Calendars such as the Chinese lunar calendar (and related calendars) * and the Hindu calendars use 60-year cycles of year names. Use one through three letters for the abbreviated * name, four for the full (wide) name, or five for the narrow name (currently the data only provides abbreviated names, * which will be used for all requested name widths). If the calendar does not provide cyclic year name data, * or if the year value to be formatted is out of the range of years for which cyclic name data is provided, * then numeric formatting is used (behaves like 'y').
4(currently also 甲子)
5(currently also 甲子)
quarterQ1..202Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four * for the full (wide) name (five for the narrow name is not yet supported).
3Q2
42nd quarter
q1..202Stand-Alone Quarter - Use one or two for the numerical quarter, three for the abbreviation, * or four for the full name (five for the narrow name is not yet supported).
3Q2
42nd quarter
monthM1..209Month - Use one or two for the numerical month, three for the abbreviation, four for * the full (wide) name, or five for the narrow name. With two (""MM""), the month number is zero-padded * if necessary (e.g. ""08"").
3Sep
4September
5S
L1..209Stand-Alone Month - Use one or two for the numerical month, three for the abbreviation, * four for the full (wide) name, or 5 for the narrow name. With two (""LL""), the month number is zero-padded if * necessary (e.g. ""08"").
3Sep
4September
5S
weekw1..227Week of Year. Use ""w"" to show the minimum number of digits, or ""ww"" to always show two digits * (zero-padding if necessary, e.g. ""08"").
W13Week of Month
dayd1..21Date - Day of the month. Use ""d"" to show the minimum number of digits, or ""dd"" to always show * two digits (zero-padding if necessary, e.g. ""08"").
D1..3345Day of year
F12Day of Week in Month. The example is for the 2nd Wed in July
g1..n2451334Modified Julian day. This is different from the conventional Julian day number in two regards. * First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number; * that is, it depends on the local time zone. It can be thought of as a single number that encompasses * all the date-related fields.
week
* day
E1..3TueDay of week - Use one through three letters for the short day, four for the full (wide) name, * five for the narrow name, or six for the short name.
4Tuesday
5T
6Tu
e1..22Local day of week. Same as E except adds a numeric value that will depend on the local * starting day of the week, using one or two letters. For this example, Monday is the first day of the week.
3Tue
4Tuesday
5T
6Tu
c12Stand-Alone local day of week - Use one letter for the local numeric value (same * as 'e'), three for the short day, four for the full (wide) name, five for the narrow name, or six for * the short name.
3Tue
4Tuesday
5T
6Tu
perioda1AMAM or PM
hourh1..211Hour [1-12]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern * generation, it should match the 12-hour-cycle format preferred by the locale (h or K); it should not match * a 24-hour-cycle format (H or k). Use hh for zero padding.
H1..213Hour [0-23]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern * generation, it should match the 24-hour-cycle format preferred by the locale (H or k); it should not match a * 12-hour-cycle format (h or K). Use HH for zero padding.
K1..20Hour [0-11]. When used in a skeleton, only matches K or h, see above. Use KK for zero padding.
k1..224Hour [1-24]. When used in a skeleton, only matches k or H, see above. Use kk for zero padding.
minutem1..259Minute. Use ""m"" to show the minimum number of digits, or ""mm"" to always show two digits * (zero-padding if necessary, e.g. ""08"")..
seconds1..212Second. Use ""s"" to show the minimum number of digits, or ""ss"" to always show two digits * (zero-padding if necessary, e.g. ""08"").
S1..n3450Fractional Second - truncates (like other time fields) to the count of letters when formatting. Appends zeros if more than 3 letters specified. Truncates at three significant digits when parsing. * (example shows display using pattern SSSS for seconds value 12.34567)
A1..n69540000Milliseconds in day. This field behaves exactly like a composite of all time-related fields, * not including the zone fields. As such, it also reflects discontinuities of those fields on DST transition * days. On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. This * reflects the fact that is must be combined with the offset field to obtain a unique local time value.
zonez1..3PDTThe short specific non-location format. * Where that is unavailable, falls back to the short localized GMT format (""O"").
4Pacific Daylight TimeThe long specific non-location format. * Where that is unavailable, falls back to the long localized GMT format (""OOOO"").
Z1..3-0800The ISO8601 basic format with hours, minutes and optional seconds fields. * The format is equivalent to RFC 822 zone format (when optional seconds field is absent). * This is equivalent to the ""xxxx"" specifier.
4GMT-8:00The long localized GMT format. * This is equivalent to the ""OOOO"" specifier.
5-08:00
* -07:52:58
The ISO8601 extended format with hours, minutes and optional seconds fields. * The ISO8601 UTC indicator ""Z"" is used when local time offset is 0. * This is equivalent to the ""XXXXX"" specifier.
O1GMT-8The short localized GMT format.
4GMT-08:00The long localized GMT format.
v1PTThe short generic non-location format. * Where that is unavailable, falls back to the generic location format (""VVVV""), * then the short localized GMT format as the final fallback.
4Pacific TimeThe long generic non-location format. * Where that is unavailable, falls back to generic location format (""VVVV""). *
V1uslaxThe short time zone ID. * Where that is unavailable, the special short time zone ID unk (Unknown Zone) is used.
* Note: This specifier was originally used for a variant of the short specific non-location format, * but it was deprecated in the later version of the LDML specification. In CLDR 23/ICU 51, the definition of * the specifier was changed to designate a short time zone ID.
2America/Los_AngelesThe long time zone ID.
3Los AngelesThe exemplar city (location) for the time zone. * Where that is unavailable, the localized exemplar city name for the special zone Etc/Unknown is used * as the fallback (for example, ""Unknown City"").
4Los Angeles TimeThe generic location format. * Where that is unavailable, falls back to the long localized GMT format (""OOOO""; * Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)
* This is especially useful when presenting possible timezone choices for user selection, * since the naming is more uniform than the ""v"" format.
X1-08
* +0530
* Z
The ISO8601 basic format with hours field and optional minutes field. * The ISO8601 UTC indicator ""Z"" is used when local time offset is 0.
2-0800
* Z
The ISO8601 basic format with hours and minutes fields. * The ISO8601 UTC indicator ""Z"" is used when local time offset is 0.
3-08:00
* Z
The ISO8601 extended format with hours and minutes fields. * The ISO8601 UTC indicator ""Z"" is used when local time offset is 0.
4-0800
* -075258
* Z
The ISO8601 basic format with hours, minutes and optional seconds fields. * (Note: The seconds field is not supported by the ISO8601 specification.) * The ISO8601 UTC indicator ""Z"" is used when local time offset is 0.
5-08:00
* -07:52:58
* Z
The ISO8601 extended format with hours, minutes and optional seconds fields. * (Note: The seconds field is not supported by the ISO8601 specification.) * The ISO8601 UTC indicator ""Z"" is used when local time offset is 0.
x1-08
* +0530
The ISO8601 basic format with hours field and optional minutes field.
2-0800The ISO8601 basic format with hours and minutes fields.
3-08:00The ISO8601 extended format with hours and minutes fields.
4-0800
* -075258
The ISO8601 basic format with hours, minutes and optional seconds fields. * (Note: The seconds field is not supported by the ISO8601 specification.)
5-08:00
* -07:52:58
The ISO8601 extended format with hours, minutes and optional seconds fields. * (Note: The seconds field is not supported by the ISO8601 specification.)
* *
*

* Any characters in the pattern that are not in the ranges of ['a'..'z'] * and ['A'..'Z'] will be treated as quoted text. For instance, characters * like ':', '.', ' ', '#' and '@' will appear in the resulting time text * even they are not embraced within single quotes. *

* A pattern containing any invalid pattern letter will result in a thrown * exception during formatting or parsing. * *

* Examples Using the US Locale: *

*
 * Format Pattern                         Result
 * --------------                         -------
 * ""yyyy.MM.dd G 'at' HH:mm:ss vvvv"" ->>  1996.07.10 AD at 15:08:56 Pacific Time
 * ""EEE, MMM d, ''yy""                ->>  Wed, July 10, '96
 * ""h:mm a""                          ->>  12:08 PM
 * ""hh 'o''clock' a, zzzz""           ->>  12 o'clock PM, Pacific Daylight Time
 * ""K:mm a, vvv""                     ->>  0:00 PM, PT
 * ""yyyyy.MMMMM.dd GGG hh:mm aaa""    ->>  01996.July.10 AD 12:08 PM
 * 
*
* Code Sample: *
*
 * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, ""PST"");
 * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
 * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
 * 
* // Format the current time. * SimpleDateFormat formatter * = new SimpleDateFormat (""yyyy.MM.dd G 'at' hh:mm:ss a zzz""); * Date currentTime_1 = new Date(); * String dateString = formatter.format(currentTime_1); *
* // Parse the previous string back into a Date. * ParsePosition pos = new ParsePosition(0); * Date currentTime_2 = formatter.parse(dateString, pos); *
*
* In the example, the time value currentTime_2 obtained from * parsing will be equal to currentTime_1. However, they may not be * equal if the am/pm marker 'a' is left out from the format pattern while * the ""hour in am/pm"" pattern symbol is used. This information loss can * happen when formatting the time in PM. * *

When parsing a date string using the abbreviated year pattern (""yy""), * SimpleDateFormat must interpret the abbreviated year * relative to some century. It does this by adjusting dates to be * within 80 years before and 20 years after the time the SimpleDateFormat * instance is created. For example, using a pattern of ""MM/dd/yy"" and a * SimpleDateFormat instance created on Jan 1, 1997, the string * ""01/11/12"" would be interpreted as Jan 11, 2012 while the string ""05/04/64"" * would be interpreted as May 4, 1964. * During parsing, only strings consisting of exactly two digits, as defined by * {@link android.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default * century. * Any other numeric string, such as a one digit string, a three or more digit * string, or a two digit string that isn't all digits (for example, ""-1""), is * interpreted literally. So ""01/02/3"" or ""01/02/003"" are parsed, using the * same pattern, as Jan 2, 3 AD. Likewise, ""01/02/-3"" is parsed as Jan 2, 4 BC. * *

If the year pattern does not have exactly two 'y' characters, the year is * interpreted literally, regardless of the number of digits. So using the * pattern ""MM/dd/yyyy"", ""01/11/12"" parses to Jan 11, 12 A.D. * *

When numeric fields abut one another directly, with no intervening delimiter * characters, they constitute a run of abutting numeric fields. Such runs are * parsed specially. For example, the format ""HHmmss"" parses the input text * ""123456"" to 12:34:56, parses the input text ""12345"" to 1:23:45, and fails to * parse ""1234"". In other words, the leftmost field of the run is flexible, * while the others keep a fixed width. If the parse fails anywhere in the run, * then the leftmost field is shortened by one character, and the entire run is * parsed again. This is repeated until either the parse succeeds or the * leftmost field is one character in length. If the parse still fails at that * point, the parse of the run fails. * *

For time zones that have no names, use strings GMT+hours:minutes or * GMT-hours:minutes. * *

The calendar defines what is the first day of the week, the first week * of the year, whether hours are zero based or not (0 vs 12 or 24), and the * time zone. There is one common decimal format to handle all the numbers; * the digit count is handled programmatically according to the pattern. * *

Synchronization

* * Date formats are not synchronized. It is recommended to create separate * format instances for each thread. If multiple threads access a format * concurrently, it must be synchronized externally. * * @see android.icu.util.Calendar * @see android.icu.util.GregorianCalendar * @see android.icu.util.TimeZone * @see DateFormat * @see DateFormatSymbols * @see DecimalFormat * @see TimeZoneFormat * @author Mark Davis, Chen-Lieh Huang, Alan Liu */ public class SimpleDateFormat extends DateFormat { // the official serial version ID which says cryptically // which version we're compatible with private static final long serialVersionUID = 4774881970558875024L; // the internal serial version which says which version was written // - 0 (default) for version up to JDK 1.1.3 // - 1 for version from JDK 1.1.4, which includes a new field // - 2 we write additional int for capitalizationSetting static final int currentSerialVersion = 2; static boolean DelayedHebrewMonthCheck = false; /* * From calendar field to its level. * Used to order calendar field. * For example, calendar fields can be defined in the following order: * year > month > date > am-pm > hour > minute * YEAR --> 10, MONTH -->20, DATE --> 30; * AM_PM -->40, HOUR --> 50, MINUTE -->60 */ private static final int[] CALENDAR_FIELD_TO_LEVEL = { /*GyM*/ 0, 10, 20, /*wW*/ 20, 30, /*dDEF*/ 30, 20, 30, 30, /*ahHm*/ 40, 50, 50, 60, /*sS*/ 70, 80, /*z?Y*/ 0, 0, 10, /*eug*/ 30, 10, 0, /*A?*/ 40, 0, 0 }; /* * From calendar field letter to its level. * Used to order calendar field. * For example, calendar fields can be defined in the following order: * year > month > date > am-pm > hour > minute * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60 */ private static final int[] PATTERN_CHAR_TO_LEVEL = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // ! "" # $ % & ' ( ) * + , - . / -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // @ A B C D E F G H I J K L M N O -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, 0, // P Q R S T U V W X Y Z [ \ ] ^ _ -1, 20, -1, 80, -1, 10, 0, 30, 0, 10, 0, -1, -1, -1, -1, -1, // ` a b c d e f g h i j k l m n o -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1, // p q r s t u v w x y z { | } ~ -1, 20, 10, 70, -1, 10, 0, 20, 0, 10, 0, -1, -1, -1, -1, -1, }; /** * Map calendar field letter into calendar field level. */ private static int getLevelFromChar(char ch) { return ch < PATTERN_CHAR_TO_LEVEL.length ? PATTERN_CHAR_TO_LEVEL[ch & 0xff] : -1; } private static final boolean[] PATTERN_CHAR_IS_SYNTAX = { // false, false, false, false, false, false, false, false, // false, false, false, false, false, false, false, false, // false, false, false, false, false, false, false, false, // false, false, false, false, false, false, false, false, // ! "" # $ % & ' false, false, false, false, false, false, false, false, // ( ) * + , - . / false, false, false, false, false, false, false, false, // 0 1 2 3 4 5 6 7 false, false, false, false, false, false, false, false, // 8 9 : ; < = > ? false, false, false, false, false, false, false, false, // @ A B C D E F G false, true, true, true, true, true, true, true, // H I J K L M N O true, true, true, true, true, true, true, true, // P Q R S T U V W true, true, true, true, true, true, true, true, // X Y Z [ \ ] ^ _ true, true, true, false, false, false, false, false, // ` a b c d e f g false, true, true, true, true, true, true, true, // h i j k l m n o true, true, true, true, true, true, true, true, // p q r s t u v w true, true, true, true, true, true, true, true, // x y z { | } ~ true, true, true, false, false, false, false, false, }; /** * Tell if a character can be used to define a field in a format string. */ private static boolean isSyntaxChar(char ch) { return ch < PATTERN_CHAR_IS_SYNTAX.length ? PATTERN_CHAR_IS_SYNTAX[ch & 0xff] : false; } // When calendar uses hebr numbering (i.e. he@calendar=hebrew), // offset the years within the current millenium down to 1-999 private static final int HEBREW_CAL_CUR_MILLENIUM_START_YEAR = 5000; private static final int HEBREW_CAL_CUR_MILLENIUM_END_YEAR = 6000; /** * The version of the serialized data on the stream. Possible values: *
    *
  • 0 or not present on stream: JDK 1.1.3. This version * has no defaultCenturyStart on stream. *
  • 1 JDK 1.1.4 or later. This version adds * defaultCenturyStart. *
  • 2 This version writes an additional int for * capitalizationSetting. *
* When streaming out this class, the most recent format * and the highest allowable serialVersionOnStream * is written. * @serial */ private int serialVersionOnStream = currentSerialVersion; /** * The pattern string of this formatter. This is always a non-localized * pattern. May not be null. See class documentation for details. * @serial */ private String pattern; /** * The override string of this formatter. Used to override the * numbering system for one or more fields. * @serial */ private String override; /** * The hash map used for number format overrides. * @serial */ private HashMap numberFormatters; /** * The hash map used for number format overrides. * @serial */ private HashMap overrideMap; /** * The symbols used by this formatter for week names, month names, * etc. May not be null. * @serial * @see DateFormatSymbols */ private DateFormatSymbols formatData; private transient ULocale locale; /** * We map dates with two-digit years into the century starting at * defaultCenturyStart, which may be any date. May * not be null. * @serial */ private Date defaultCenturyStart; private transient int defaultCenturyStartYear; // defaultCenturyBase is set when an instance is created // and may be used for calculating defaultCenturyStart when needed. private transient long defaultCenturyBase; private static final int millisPerHour = 60 * 60 * 1000; // When possessing ISO format, the ERA may be ommitted is the // year specifier is a negative number. private static final int ISOSpecialEra = -32000; // This prefix is designed to NEVER MATCH real text, in order to // suppress the parsing of negative numbers. Adjust as needed (if // this becomes valid Unicode). private static final String SUPPRESS_NEGATIVE_PREFIX = ""\uAB00""; /** * If true, this object supports fast formatting using the * subFormat variant that takes a StringBuffer. */ private transient boolean useFastFormat; /* * The time zone sub-formatter, introduced in ICU 4.8 */ private volatile TimeZoneFormat tzFormat; /** * BreakIterator to use for capitalization */ private transient BreakIterator capitalizationBrkIter = null; /** * DateFormat pattern contains the minute field. */ private transient boolean hasMinute; /** * DateFormat pattern contains the second field. */ private transient boolean hasSecond; /* * Capitalization setting, introduced in ICU 50 * Special serialization, see writeObject & readObject below * * Hoisted to DateFormat in ICU 53, get value with * getContext(DisplayContext.Type.CAPITALIZATION) */ // private transient DisplayContext capitalizationSetting; /* * Old defaultCapitalizationContext field * from ICU 49.1: */ //private ContextValue defaultCapitalizationContext; /** * Old ContextValue enum, preserved only to avoid * deserialization errs from ICU 49.1. */ @SuppressWarnings(""unused"") private enum ContextValue { UNKNOWN, CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE, CAPITALIZATION_FOR_UI_LIST_OR_MENU, CAPITALIZATION_FOR_STANDALONE } /** * Constructs a SimpleDateFormat using the default pattern for the default FORMAT * locale. Note: Not all locales support SimpleDateFormat; for full * generality, use the factory methods in the DateFormat class. * * @see DateFormat * @see Category#FORMAT */ public SimpleDateFormat() { this(getDefaultPattern(), null, null, null, null, true, null); } /** * Constructs a SimpleDateFormat using the given pattern in the default FORMAT * locale. Note: Not all locales support SimpleDateFormat; for full * generality, use the factory methods in the DateFormat class. * @see Category#FORMAT */ public SimpleDateFormat(String pattern) { this(pattern, null, null, null, null, true, null); } /** * Constructs a SimpleDateFormat using the given pattern and locale. * Note: Not all locales support SimpleDateFormat; for full * generality, use the factory methods in the DateFormat class. */ public SimpleDateFormat(String pattern, Locale loc) { this(pattern, null, null, null, ULocale.forLocale(loc), true, null); } /** * Constructs a SimpleDateFormat using the given pattern and locale. * Note: Not all locales support SimpleDateFormat; for full * generality, use the factory methods in the DateFormat class. */ public SimpleDateFormat(String pattern, ULocale loc) { this(pattern, null, null, null, loc, true, null); } /** * Constructs a SimpleDateFormat using the given pattern , override and locale. * @param pattern The pattern to be used * @param override The override string. A numbering system override string can take one of the following forms: * 1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern. * 2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern * followed by an = sign, followed by the numbering system name. For example, to specify that just the year * be formatted using Hebrew digits, use the override ""y=hebr"". Multiple overrides can be specified in a single * string by separating them with a semi-colon. For example, the override string ""m=thai;y=deva"" would format using * Thai digits for the month and Devanagari digits for the year. * @param loc The locale to be used */ public SimpleDateFormat(String pattern, String override, ULocale loc) { this(pattern, null, null, null, loc, false,override); } /** * Constructs a SimpleDateFormat using the given pattern and * locale-specific symbol data. * Warning: uses default FORMAT locale for digits! */ public SimpleDateFormat(String pattern, DateFormatSymbols formatData) { this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null); } /** * @deprecated This API is ICU internal only. * @hide original deprecated declaration * @hide draft / provisional / internal are hidden on Android */ @Deprecated public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc) { this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null); } /** * Package-private constructor that allows a subclass to specify * whether it supports fast formatting. * * TODO make this API public. */ SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale, boolean useFastFormat, String override) { this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override); } /* * The constructor called from all other SimpleDateFormat constructors */ private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) { this.pattern = pattern; this.formatData = formatData; this.calendar = calendar; this.numberFormat = numberFormat; this.locale = locale; // time zone formatting this.useFastFormat = useFastFormat; this.override = override; initialize(); } /** * Creates an instance of SimpleDateFormat for the given format configuration * @param formatConfig the format configuration * @return A SimpleDateFormat instance * @deprecated This API is ICU internal only. * @hide original deprecated declaration * @hide draft / provisional / internal are hidden on Android */ @Deprecated public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) { String ostr = formatConfig.getOverrideString(); boolean useFast = ( ostr != null && ostr.length() > 0 ); return new SimpleDateFormat(formatConfig.getPatternString(), formatConfig.getDateFormatSymbols(), formatConfig.getCalendar(), null, formatConfig.getLocale(), useFast, formatConfig.getOverrideString()); } /* * Initialized fields */ private void initialize() { if (locale == null) { locale = ULocale.getDefault(Category.FORMAT); } if (formatData == null) { formatData = new DateFormatSymbols(locale); } if (calendar == null) { calendar = Calendar.getInstance(locale); } if (numberFormat == null) { NumberingSystem ns = NumberingSystem.getInstance(locale); if (ns.isAlgorithmic()) { numberFormat = NumberFormat.getInstance(locale); } else { String digitString = ns.getDescription(); String nsName = ns.getName(); // Use a NumberFormat optimized for date formatting numberFormat = new DateNumberFormat(locale, digitString, nsName); } } // Note: deferring calendar calculation until when we really need it. // Instead, we just record time of construction for backward compatibility. defaultCenturyBase = System.currentTimeMillis(); setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE)); initLocalZeroPaddingNumberFormat(); if (override != null) { initNumberFormatters(locale); } parsePattern(); } /** * Private method lazily instantiate the TimeZoneFormat field * @param bForceUpdate when true, check if tzFormat is synchronized with * the current numberFormat and update its digits if necessary. When false, * this check is skipped. */ private synchronized void initializeTimeZoneFormat(boolean bForceUpdate) { if (bForceUpdate || tzFormat == null) { tzFormat = TimeZoneFormat.getInstance(locale); String digits = null; if (numberFormat instanceof DecimalFormat) { DecimalFormatSymbols decsym = ((DecimalFormat) numberFormat).getDecimalFormatSymbols(); digits = new String(decsym.getDigits()); } else if (numberFormat instanceof DateNumberFormat) { digits = new String(((DateNumberFormat)numberFormat).getDigits()); } if (digits != null) { if (!tzFormat.getGMTOffsetDigits().equals(digits)) { if (tzFormat.isFrozen()) { tzFormat = tzFormat.cloneAsThawed(); } tzFormat.setGMTOffsetDigits(digits); } } } } /** * Private method, returns non-null TimeZoneFormat. * @return the TimeZoneFormat used by this formatter. */ private TimeZoneFormat tzFormat() { if (tzFormat == null) { initializeTimeZoneFormat(false); } return tzFormat; } // privates for the default pattern private static ULocale cachedDefaultLocale = null; private static String cachedDefaultPattern = null; private static final String FALLBACKPATTERN = ""yy/MM/dd HH:mm""; /* * Returns the default date and time pattern (SHORT) for the default locale. * This method is only used by the default SimpleDateFormat constructor. */ private static synchronized String getDefaultPattern() { ULocale defaultLocale = ULocale.getDefault(Category.FORMAT); if (!defaultLocale.equals(cachedDefaultLocale)) { cachedDefaultLocale = defaultLocale; Calendar cal = Calendar.getInstance(cachedDefaultLocale); try { // Load the calendar data directly. ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance( ICUData.ICU_BASE_NAME, cachedDefaultLocale); String resourcePath = ""calendar/"" + cal.getType() + ""/DateTimePatterns""; ICUResourceBundle patternsRb= rb.findWithFallback(resourcePath); if (patternsRb == null) { patternsRb = rb.findWithFallback(""calendar/gregorian/DateTimePatterns""); } if (patternsRb == null || patternsRb.getSize() < 9) { cachedDefaultPattern = FALLBACKPATTERN; } else { int defaultIndex = 8; if (patternsRb.getSize() >= 13) { defaultIndex += (SHORT + 1); } String basePattern = patternsRb.getString(defaultIndex); cachedDefaultPattern = SimpleFormatterImpl.formatRawPattern( basePattern, 2, 2, patternsRb.getString(SHORT), patternsRb.getString(SHORT + 4)); } } catch (MissingResourceException e) { cachedDefaultPattern = FALLBACKPATTERN; } } return cachedDefaultPattern; } /* Define one-century window into which to disambiguate dates using * two-digit years. */ private void parseAmbiguousDatesAsAfter(Date startDate) { defaultCenturyStart = startDate; calendar.setTime(startDate); defaultCenturyStartYear = calendar.get(Calendar.YEAR); } /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time. * The default start time is 80 years before the creation time of this object. */ private void initializeDefaultCenturyStart(long baseTime) { defaultCenturyBase = baseTime; // clone to avoid messing up date stored in calendar object // when this method is called while parsing Calendar tmpCal = (Calendar)calendar.clone(); tmpCal.setTimeInMillis(baseTime); tmpCal.add(Calendar.YEAR, -80); defaultCenturyStart = tmpCal.getTime(); defaultCenturyStartYear = tmpCal.get(Calendar.YEAR); } /* Gets the default century start date for this object */ private Date getDefaultCenturyStart() { if (defaultCenturyStart == null) { // not yet initialized initializeDefaultCenturyStart(defaultCenturyBase); } return defaultCenturyStart; } /* Gets the default century start year for this object */ private int getDefaultCenturyStartYear() { if (defaultCenturyStart == null) { // not yet initialized initializeDefaultCenturyStart(defaultCenturyBase); } return defaultCenturyStartYear; } /** * Sets the 100-year period 2-digit years will be interpreted as being in * to begin on the date the user specifies. * @param startDate During parsing, two digit years will be placed in the range * startDate to startDate + 100 years. */ public void set2DigitYearStart(Date startDate) { parseAmbiguousDatesAsAfter(startDate); } /** * Returns the beginning date of the 100-year period 2-digit years are interpreted * as being within. * @return the start of the 100-year period into which two digit years are * parsed */ public Date get2DigitYearStart() { return getDefaultCenturyStart(); } /** * [icu] Set a particular DisplayContext value in the formatter, * such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see * DateFormat. * * @param context The DisplayContext value to set. */ // Here we override the DateFormat implementation in order to lazily initialize relevant items @Override public void setContext(DisplayContext context) { super.setContext(context); if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE || context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU || context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) { capitalizationBrkIter = BreakIterator.getSentenceInstance(locale); } } /** * Formats a date or time, which is the standard millis * since January 1, 1970, 00:00:00 GMT. *

Example: using the US locale: * ""yyyy.MM.dd G 'at' HH:mm:ss zzz"" ->> 1996.07.10 AD at 15:08:56 PDT * @param cal the calendar whose date-time value is to be formatted into a date-time string * @param toAppendTo where the new date-time text is to be appended * @param pos the formatting position. On input: an alignment field, * if desired. On output: the offsets of the alignment field. * @return the formatted date-time string. * @see DateFormat */ @Override public StringBuffer format(Calendar cal, StringBuffer toAppendTo, FieldPosition pos) { TimeZone backupTZ = null; if (cal != calendar && !cal.getType().equals(calendar.getType())) { // Different calendar type // We use the time and time zone from the input calendar, but // do not use the input calendar for field calculation. calendar.setTimeInMillis(cal.getTimeInMillis()); backupTZ = calendar.getTimeZone(); calendar.setTimeZone(cal.getTimeZone()); cal = calendar; } StringBuffer result = format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, null); if (backupTZ != null) { // Restore the original time zone calendar.setTimeZone(backupTZ); } return result; } // The actual method to format date. If List attributes is not null, // then attribute information will be recorded. private StringBuffer format(Calendar cal, DisplayContext capitalizationContext, StringBuffer toAppendTo, FieldPosition pos, List attributes) { // Initialize pos.setBeginIndex(0); pos.setEndIndex(0); // Careful: For best performance, minimize the number of calls // to StringBuffer.append() by consolidating appends when // possible. Object[] items = getPatternItems(); for (int i = 0; i < items.length; i++) { if (items[i] instanceof String) { toAppendTo.append((String)items[i]); } else { PatternItem item = (PatternItem)items[i]; int start = 0; if (attributes != null) { // Save the current length start = toAppendTo.length(); } if (useFastFormat) { subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), i, capitalizationContext, pos, cal); } else { toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), i, capitalizationContext, pos, cal)); } if (attributes != null) { // Check the sub format length int end = toAppendTo.length(); if (end - start > 0) { // Append the attribute to the list DateFormat.Field attr = patternCharToDateFormatField(item.type); FieldPosition fp = new FieldPosition(attr); fp.setBeginIndex(start); fp.setEndIndex(end); attributes.add(fp); } } } } return toAppendTo; } // Map pattern character to index private static final int[] PATTERN_CHAR_TO_INDEX = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // ! "" # $ % & ' ( ) * + , - . / -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // @ A B C D E F G H I J K L M N O -1, 22, 36, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, 31, // P Q R S T U V W X Y Z [ \ ] ^ _ -1, 27, -1, 8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1, // ` a b c d e f g h i j k l m n o -1, 14, 35, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1, // p q r s t u v w x y z { | } ~ -1, 28, 34, 7, -1, 20, 24, 12, 33, 1, 17, -1, -1, -1, -1, -1, }; private static int getIndexFromChar(char ch) { return ch < PATTERN_CHAR_TO_INDEX.length ? PATTERN_CHAR_TO_INDEX[ch & 0xff] : -1; } // Map pattern character index to Calendar field number private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = { /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH, /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND, /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH, /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM, /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET, /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR, /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET /* also DST_OFFSET */, /*v*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, /*c*/ Calendar.DOW_LOCAL, /*L*/ Calendar.MONTH, /*Qq*/ Calendar.MONTH, Calendar.MONTH, /*V*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, /*U*/ Calendar.YEAR, /*O*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, /*Xx*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, Calendar.ZONE_OFFSET /* also DST_OFFSET */, /*r*/ Calendar.EXTENDED_YEAR /* not an exact match */, /*bB*/ -1, -1 /* am/pm/midnight/noon and flexible day period fields; no mapping to calendar fields */ /*:*/ -1, /* => no useful mapping to any calendar field, can't use protected Calendar.BASE_FIELD_COUNT */ }; // Map pattern character index to DateFormat field number private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD, /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD, /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD, /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD, /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD, /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD, /*c*/ DateFormat.STANDALONE_DAY_FIELD, /*L*/ DateFormat.STANDALONE_MONTH_FIELD, /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD, /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD, /*U*/ DateFormat.YEAR_NAME_FIELD, /*O*/ DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD, /*Xx*/ DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD, /*r*/ DateFormat.RELATED_YEAR, /*bB*/ DateFormat.AM_PM_MIDNIGHT_NOON_FIELD, DateFormat.FLEXIBLE_DAY_PERIOD_FIELD, /*(no pattern character defined for this)*/ DateFormat.TIME_SEPARATOR, }; // Map pattern character index to DateFormat.Field private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = { /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH, /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0, /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND, /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH, /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM, /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE, /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR, /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE, /*v*/ DateFormat.Field.TIME_ZONE, /*c*/ DateFormat.Field.DAY_OF_WEEK, /*L*/ DateFormat.Field.MONTH, /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER, /*V*/ DateFormat.Field.TIME_ZONE, /*U*/ DateFormat.Field.YEAR, /*O*/ DateFormat.Field.TIME_ZONE, /*Xx*/ DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE, /*r*/ DateFormat.Field.RELATED_YEAR, /*bB*/ DateFormat.Field.AM_PM_MIDNIGHT_NOON, DateFormat.Field.FLEXIBLE_DAY_PERIOD, /*(no pattern character defined for this)*/ DateFormat.Field.TIME_SEPARATOR, }; /** * Returns a DateFormat.Field constant associated with the specified format pattern * character. * * @param ch The pattern character * @return DateFormat.Field associated with the pattern character */ protected DateFormat.Field patternCharToDateFormatField(char ch) { int patternCharIndex = getIndexFromChar(ch); if (patternCharIndex != -1) { return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]; } return null; } /** * Formats a single field, given its pattern character. Subclasses may * override this method in order to modify or add formatting * capabilities. * @param ch the pattern character * @param count the number of times ch is repeated in the pattern * @param beginOffset the offset of the output string at the start of * this field; used to set pos when appropriate * @param pos receives the position of a field, when appropriate * @param fmtData the symbols for this formatter */ protected String subFormat(char ch, int count, int beginOffset, FieldPosition pos, DateFormatSymbols fmtData, Calendar cal) throws IllegalArgumentException { // Note: formatData is ignored return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal); } /** * Formats a single field. This is the version called internally; it * adds fieldNum and capitalizationContext parameters. * * @deprecated This API is ICU internal only. * @hide original deprecated declaration * @hide draft / provisional / internal are hidden on Android */ @Deprecated protected String subFormat(char ch, int count, int beginOffset, int fieldNum, DisplayContext capitalizationContext, FieldPosition pos, Calendar cal) { StringBuffer buf = new StringBuffer(); subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal); return buf.toString(); } /** * Formats a single field; useFastFormat variant. Reuses a * StringBuffer for results instead of creating a String on the * heap for each call. * * NOTE We don't really need the beginOffset parameter, EXCEPT for * the need to support the slow subFormat variant (above) which * has to pass it in to us. * * @deprecated This API is ICU internal only. * @hide original deprecated declaration * @hide draft / provisional / internal are hidden on Android */ @Deprecated @SuppressWarnings(""fallthrough"") protected void subFormat(StringBuffer buf, char ch, int count, int beginOffset, int fieldNum, DisplayContext capitalizationContext, FieldPosition pos, Calendar cal) { final int maxIntCount = Integer.MAX_VALUE; final int bufstart = buf.length(); TimeZone tz = cal.getTimeZone(); long date = cal.getTimeInMillis(); String result = null; int patternCharIndex = getIndexFromChar(ch); if (patternCharIndex == -1) { if (ch == 'l') { // (SMALL LETTER L) deprecated placeholder for leap month marker, ignore return; } else { throw new IllegalArgumentException(""Illegal pattern character "" + ""'"" + ch + ""' in \"""" + pattern + '""'); } } final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; int value = 0; // Don't get value unless it is useful if (field >= 0) { value = (patternCharIndex != DateFormat.RELATED_YEAR)? cal.get(field): cal.getRelatedYear(); } NumberFormat currentNumberFormat = getNumberFormat(ch); DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER; switch (patternCharIndex) { case 0: // 'G' - ERA if ( cal.getType().equals(""chinese"") || cal.getType().equals(""dangi"") ) { // moved from ChineseDateFormat zeroPaddingNumber(currentNumberFormat, buf, value, 1, 9); } else { if (count == 5) { safeAppend(formatData.narrowEras, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_NARROW; } else if (count == 4) { safeAppend(formatData.eraNames, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_WIDE; } else { safeAppend(formatData.eras, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_ABBREV; } } break; case 30: // 'U' - YEAR_NAME_FIELD if (formatData.shortYearNames != null && value <= formatData.shortYearNames.length) { safeAppend(formatData.shortYearNames, value-1, buf); break; } // else fall through to numeric year handling, do not break here case 1: // 'y' - YEAR case 18: // 'Y' - YEAR_WOY if ( override != null && (override.compareTo(""hebr"") == 0 || override.indexOf(""y=hebr"") >= 0) && value > HEBREW_CAL_CUR_MILLENIUM_START_YEAR && value < HEBREW_CAL_CUR_MILLENIUM_END_YEAR ) { value -= HEBREW_CAL_CUR_MILLENIUM_START_YEAR; } /* According to the specification, if the number of pattern letters ('y') is 2, * the year is truncated to 2 digits; otherwise it is interpreted as a number. * But the original code process 'y', 'yy', 'yyy' in the same way. and process * patterns with 4 or more than 4 'y' characters in the same way. * So I change the codes to meet the specification. [Richard/GCl] */ if (count == 2) { zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96 } else { //count = 1 or count > 2 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); } break; case 2: // 'M' - MONTH case 26: // 'L' - STANDALONE MONTH if ( cal.getType().equals(""hebrew"")) { boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR)); if (isLeap && value == 6 && count >= 3 ) { value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar. } if (!isLeap && value >= 6 && count < 3 ) { value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7. } } int isLeapMonth = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT)? cal.get(Calendar.IS_LEAP_MONTH): 0; // should consolidate the next section by using arrays of pointers & counts for the right symbols... if (count == 5) { if (patternCharIndex == 2) { safeAppendWithMonthPattern(formatData.narrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_NARROW]: null); } else { safeAppendWithMonthPattern(formatData.standaloneNarrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW]: null); } capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_NARROW; } else if (count == 4) { if (patternCharIndex == 2) { safeAppendWithMonthPattern(formatData.months, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT; } else { safeAppendWithMonthPattern(formatData.standaloneMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE; } } else if (count == 3) { if (patternCharIndex == 2) { safeAppendWithMonthPattern(formatData.shortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT; } else { safeAppendWithMonthPattern(formatData.standaloneShortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE; } } else { StringBuffer monthNumber = new StringBuffer(); zeroPaddingNumber(currentNumberFormat, monthNumber, value+1, count, maxIntCount); String[] monthNumberStrings = new String[1]; monthNumberStrings[0] = monthNumber.toString(); safeAppendWithMonthPattern(monthNumberStrings, 0, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC]: null); } break; case 4: // 'k' - HOUR_OF_DAY (1..24) if (value == 0) { zeroPaddingNumber(currentNumberFormat,buf, cal.getMaximum(Calendar.HOUR_OF_DAY)+1, count, maxIntCount); } else { zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); } break; case 8: // 'S' - FRACTIONAL_SECOND // Fractional seconds left-justify { numberFormat.setMinimumIntegerDigits(Math.min(3, count)); numberFormat.setMaximumIntegerDigits(maxIntCount); if (count == 1) { value /= 100; } else if (count == 2) { value /= 10; } FieldPosition p = new FieldPosition(-1); numberFormat.format(value, buf, p); if (count > 3) { numberFormat.setMinimumIntegerDigits(count - 3); numberFormat.format(0L, buf, p); } } break; case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names) if (count < 3) { zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); break; } // For alpha day-of-week, we don't want DOW_LOCAL, // we need the standard DAY_OF_WEEK. value = cal.get(Calendar.DAY_OF_WEEK); // fall through, do not break here case 9: // 'E' - DAY_OF_WEEK if (count == 5) { safeAppend(formatData.narrowWeekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW; } else if (count == 4) { safeAppend(formatData.weekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT; } else if (count == 6 && formatData.shorterWeekdays != null) { safeAppend(formatData.shorterWeekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT; } else {// count <= 3, use abbreviated form if exists safeAppend(formatData.shortWeekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT; } break; case 14: // 'a' - AM_PM // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version if (count < 5 || formatData.ampmsNarrow == null) { safeAppend(formatData.ampms, value, buf); } else { safeAppend(formatData.ampmsNarrow, value, buf); } break; case 15: // 'h' - HOUR (1..12) if (value == 0) { zeroPaddingNumber(currentNumberFormat,buf, cal.getLeastMaximum(Calendar.HOUR)+1, count, maxIntCount); } else { zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); } break; case 17: // 'z' - TIMEZONE_FIELD if (count < 4) { // ""z"", ""zz"", ""zzz"" result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT; } else { result = tzFormat().format(Style.SPECIFIC_LONG, tz, date); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG; } buf.append(result); break; case 23: // 'Z' - TIMEZONE_RFC_FIELD if (count < 4) { // RFC822 format - equivalent to ISO 8601 local offset fixed width format result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date); } else if (count == 5) { // ISO 8601 extended format result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date); } else { // long form, localized GMT pattern result = tzFormat().format(Style.LOCALIZED_GMT, tz, date); } buf.append(result); break; case 24: // 'v' - TIMEZONE_GENERIC_FIELD if (count == 1) { // ""v"" result = tzFormat().format(Style.GENERIC_SHORT, tz, date); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT; } else if (count == 4) { // ""vvvv"" result = tzFormat().format(Style.GENERIC_LONG, tz, date); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG; } buf.append(result); break; case 29: // 'V' - TIMEZONE_SPECIAL_FIELD if (count == 1) { // ""V"" result = tzFormat().format(Style.ZONE_ID_SHORT, tz, date); } else if (count == 2) { // ""VV"" result = tzFormat().format(Style.ZONE_ID, tz, date); } else if (count == 3) { // ""VVV"" result = tzFormat().format(Style.EXEMPLAR_LOCATION, tz, date); } else if (count == 4) { // ""VVVV"" result = tzFormat().format(Style.GENERIC_LOCATION, tz, date); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG; } buf.append(result); break; case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD if (count == 1) { // ""O"" - Short Localized GMT format result = tzFormat().format(Style.LOCALIZED_GMT_SHORT, tz, date); } else if (count == 4) { // ""OOOO"" - Localized GMT format result = tzFormat().format(Style.LOCALIZED_GMT, tz, date); } buf.append(result); break; case 32: // 'X' - TIMEZONE_ISO_FIELD if (count == 1) { // ""X"" - ISO Basic/Short result = tzFormat().format(Style.ISO_BASIC_SHORT, tz, date); } else if (count == 2) { // ""XX"" - ISO Basic/Fixed result = tzFormat().format(Style.ISO_BASIC_FIXED, tz, date); } else if (count == 3) { // ""XXX"" - ISO Extended/Fixed result = tzFormat().format(Style.ISO_EXTENDED_FIXED, tz, date); } else if (count == 4) { // ""XXXX"" - ISO Basic/Optional second field result = tzFormat().format(Style.ISO_BASIC_FULL, tz, date); } else if (count == 5) { // ""XXXXX"" - ISO Extended/Optional second field result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date); } buf.append(result); break; case 33: // 'x' - TIMEZONE_ISO_LOCAL_FIELD if (count == 1) { // ""x"" - ISO Local Basic/Short result = tzFormat().format(Style.ISO_BASIC_LOCAL_SHORT, tz, date); } else if (count == 2) { // ""x"" - ISO Local Basic/Fixed result = tzFormat().format(Style.ISO_BASIC_LOCAL_FIXED, tz, date); } else if (count == 3) { // ""xxx"" - ISO Local Extended/Fixed result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FIXED, tz, date); } else if (count == 4) { // ""xxxx"" - ISO Local Basic/Optional second field result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date); } else if (count == 5) { // ""xxxxx"" - ISO Local Extended/Optional second field result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FULL, tz, date); } buf.append(result); break; case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone) if (count < 3) { zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount); break; } // For alpha day-of-week, we don't want DOW_LOCAL, // we need the standard DAY_OF_WEEK. value = cal.get(Calendar.DAY_OF_WEEK); if (count == 5) { safeAppend(formatData.standaloneNarrowWeekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW; } else if (count == 4) { safeAppend(formatData.standaloneWeekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE; } else if (count == 6 && formatData.standaloneShorterWeekdays != null) { safeAppend(formatData.standaloneShorterWeekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE; } else { // count == 3 safeAppend(formatData.standaloneShortWeekdays, value, buf); capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE; } break; case 27: // 'Q' - QUARTER if (count >= 4) { safeAppend(formatData.quarters, value/3, buf); } else if (count == 3) { safeAppend(formatData.shortQuarters, value/3, buf); } else { zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount); } break; case 28: // 'q' - STANDALONE QUARTER if (count >= 4) { safeAppend(formatData.standaloneQuarters, value/3, buf); } else if (count == 3) { safeAppend(formatData.standaloneShortQuarters, value/3, buf); } else { zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount); } break; case 35: // 'b' - am/pm/noon/midnight { // Note: ""midnight"" can be ambiguous as to whether it refers to beginning of day or end of day. // For ICU 57 output of ""midnight"" is temporarily suppressed. int hour = cal.get(Calendar.HOUR_OF_DAY); String toAppend = null; // For ""midnight"" and ""noon"": // Time, as displayed, must be exactly noon or midnight. // This means minutes and seconds, if present, must be zero. if ((/*hour == 0 ||*/ hour == 12) && (!hasMinute || cal.get(Calendar.MINUTE) == 0) && (!hasSecond || cal.get(Calendar.SECOND) == 0)) { // Stealing am/pm value to use as our array index. // It works out: am/midnight are both 0, pm/noon are both 1, // 12 am is 12 midnight, and 12 pm is 12 noon. value = cal.get(Calendar.AM_PM); if (count == 3) { toAppend = formatData.abbreviatedDayPeriods[value]; } else if (count == 4 || count > 5) { toAppend = formatData.wideDayPeriods[value]; } else { // count == 5 toAppend = formatData.narrowDayPeriods[value]; } } if (toAppend == null) { // Time isn't exactly midnight or noon (as displayed) or localized string doesn't // exist for requested period. Fall back to am/pm instead. subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); } else { buf.append(toAppend); } break; } case 36: // 'B' - flexible day period { // TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first // loading of an instance) if a relevant pattern character (b or B) is used. DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale()); if (ruleSet == null) { // Data doesn't exist for the locale we're looking for. // Fall back to am/pm. subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); break; } // Get current display time. int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = 0; int second = 0; if (hasMinute) { minute = cal.get(Calendar.MINUTE); } if (hasSecond) { second = cal.get(Calendar.SECOND); } // Determine day period. DayPeriodRules.DayPeriod periodType; if (hour == 0 && minute == 0 && second == 0 && ruleSet.hasMidnight()) { periodType = DayPeriodRules.DayPeriod.MIDNIGHT; } else if (hour == 12 && minute == 0 && second == 0 && ruleSet.hasNoon()) { periodType = DayPeriodRules.DayPeriod.NOON; } else { periodType = ruleSet.getDayPeriodForHour(hour); } // Note: ""midnight"" can be ambiguous as to whether it refers to beginning of day or end of day. // For ICU 57 output of ""midnight"" is temporarily suppressed. // Rule set exists, therefore periodType can't be null. // Get localized string. assert(periodType != null); String toAppend = null; int index; if (periodType != DayPeriodRules.DayPeriod.AM && periodType != DayPeriodRules.DayPeriod.PM && periodType != DayPeriodRules.DayPeriod.MIDNIGHT) { index = periodType.ordinal(); if (count <= 3) { toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short } else if (count == 4 || count > 5) { toAppend = formatData.wideDayPeriods[index]; } else { // count == 5 toAppend = formatData.narrowDayPeriods[index]; } } // Fallback schedule: // Midnight/Noon -> General Periods -> AM/PM. // Midnight/Noon -> General Periods. if (toAppend == null && (periodType == DayPeriodRules.DayPeriod.MIDNIGHT || periodType == DayPeriodRules.DayPeriod.NOON)) { periodType = ruleSet.getDayPeriodForHour(hour); index = periodType.ordinal(); if (count <= 3) { toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short } else if (count == 4 || count > 5) { toAppend = formatData.wideDayPeriods[index]; } else { // count == 5 toAppend = formatData.narrowDayPeriods[index]; } } // General Periods -> AM/PM. if (periodType == DayPeriodRules.DayPeriod.AM || periodType == DayPeriodRules.DayPeriod.PM || toAppend == null) { subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); } else { buf.append(toAppend); } break; } case 37: // TIME SEPARATOR (no pattern character currently defined, we should // not get here but leave support in for future definition. buf.append(formatData.getTimeSeparatorString()); break; default: // case 3: // 'd' - DATE // case 5: // 'H' - HOUR_OF_DAY (0..23) // case 6: // 'm' - MINUTE // case 7: // 's' - SECOND // case 10: // 'D' - DAY_OF_YEAR // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH // case 12: // 'w' - WEEK_OF_YEAR // case 13: // 'W' - WEEK_OF_MONTH // case 16: // 'K' - HOUR (0..11) // case 20: // 'u' - EXTENDED_YEAR // case 21: // 'g' - JULIAN_DAY // case 22: // 'A' - MILLISECONDS_IN_DAY zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); break; } // switch (patternCharIndex) if (fieldNum == 0 && capitalizationContext != null && UCharacter.isLowerCase(buf.codePointAt(bufstart))) { boolean titlecase = false; switch (capitalizationContext) { case CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE: titlecase = true; break; case CAPITALIZATION_FOR_UI_LIST_OR_MENU: case CAPITALIZATION_FOR_STANDALONE: if (formatData.capitalization != null) { boolean[] transforms = formatData.capitalization.get(capContextUsageType); titlecase = (capitalizationContext==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)? transforms[0]: transforms[1]; } break; default: break; } if (titlecase) { if (capitalizationBrkIter == null) { // should only happen when deserializing, etc. capitalizationBrkIter = BreakIterator.getSentenceInstance(locale); } String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, capitalizationBrkIter, UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT); buf.replace(bufstart, buf.length(), firstFieldTitleCase); } } // Set the FieldPosition (for the first occurrence only) if (pos.getBeginIndex() == pos.getEndIndex()) { if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) { pos.setBeginIndex(beginOffset); pos.setEndIndex(beginOffset + buf.length() - bufstart); } else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) { pos.setBeginIndex(beginOffset); pos.setEndIndex(beginOffset + buf.length() - bufstart); } } } private static void safeAppend(String[] array, int value, StringBuffer appendTo) { if (array != null && value >= 0 && value < array.length) { appendTo.append(array[value]); } } private static void safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern) { if (array != null && value >= 0 && value < array.length) { if (monthPattern == null) { appendTo.append(array[value]); } else { String s = SimpleFormatterImpl.formatRawPattern(monthPattern, 1, 1, array[value]); appendTo.append(s); } } } /* * PatternItem store parsed date/time field pattern information. */ private static class PatternItem { final char type; final int length; final boolean isNumeric; PatternItem(char type, int length) { this.type = type; this.length = length; isNumeric = isNumeric(type, length); } } private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache(); private transient Object[] patternItems; /* * Returns parsed pattern items. Each item is either String or * PatternItem. */ private Object[] getPatternItems() { if (patternItems != null) { return patternItems; } patternItems = PARSED_PATTERN_CACHE.get(pattern); if (patternItems != null) { return patternItems; } boolean isPrevQuote = false; boolean inQuote = false; StringBuilder text = new StringBuilder(); char itemType = 0; // 0 for string literal, otherwise date/time pattern character int [MASK] = 1; List items = new ArrayList(); for (int i = 0; i < pattern.length(); i++) { char ch = pattern.charAt(i); if (ch == '\'') { if (isPrevQuote) { text.append('\''); isPrevQuote = false; } else { isPrevQuote = true; if (itemType != 0) { items.add(new PatternItem(itemType, [MASK] )); itemType = 0; } } inQuote = !inQuote; } else { isPrevQuote = false; if (inQuote) { text.append(ch); } else { if (isSyntaxChar(ch)) { // a date/time pattern character if (ch == itemType) { [MASK] ++; } else { if (itemType == 0) { if (text.length() > 0) { items.add(text.toString()); text.setLength(0); } } else { items.add(new PatternItem(itemType, [MASK] )); } itemType = ch; [MASK] = 1; } } else { // a string literal if (itemType != 0) { items.add(new PatternItem(itemType, [MASK] )); itemType = 0; } text.append(ch); } } } } // handle last item if (itemType == 0) { if (text.length() > 0) { items.add(text.toString()); text.setLength(0); } } else { items.add(new PatternItem(itemType, [MASK] )); } patternItems = items.toArray(new Object[items.size()]); PARSED_PATTERN_CACHE.put(pattern, patternItems); return patternItems; } /** * Internal high-speed method. Reuses a StringBuffer for results * instead of creating a String on the heap for each call. * @deprecated This API is ICU internal only. * @hide original deprecated declaration * @hide draft / provisional / internal are hidden on Android */ @Deprecated protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value, int minDigits, int maxDigits) { // Note: Indian calendar uses negative value for a calendar // field. fastZeroPaddingNumber cannot handle negative numbers. // BTW, it looks like a design bug in the Indian calendar... if (useLocalZeroPaddingNumberFormat && value >= 0) { fastZeroPaddingNumber(buf, value, minDigits, maxDigits); } else { nf.setMinimumIntegerDigits(minDigits); nf.setMaximumIntegerDigits(maxDigits); nf.format(value, buf, new FieldPosition(-1)); } } /** * Overrides superclass method and * This method also clears per field NumberFormat instances * previously set by {@link #setNumberFormat(String, NumberFormat)} */ @Override public void setNumberFormat(NumberFormat newNumberFormat) { // Override this method to update local zero padding number formatter super.setNumberFormat(newNumberFormat); initLocalZeroPaddingNumberFormat(); initializeTimeZoneFormat(true); if (numberFormatters != null) { numberFormatters = null; } if (overrideMap != null) { overrideMap = null; } } /* * Initializes transient fields for fast simple numeric formatting * code. This method should be called whenever number format is updated. */ private void initLocalZeroPaddingNumberFormat() { if (numberFormat instanceof DecimalFormat) { decDigits = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getDigits(); useLocalZeroPaddingNumberFormat = true; } else if (numberFormat instanceof DateNumberFormat) { decDigits = ((DateNumberFormat)numberFormat).getDigits(); useLocalZeroPaddingNumberFormat = true; } else { useLocalZeroPaddingNumberFormat = false; } if (useLocalZeroPaddingNumberFormat) { decimalBuf = new char[DECIMAL_BUF_SIZE]; } } // If true, use local version of zero padding number format private transient boolean useLocalZeroPaddingNumberFormat; private transient char[] decDigits; // read-only - can be shared by multiple instances private transient char[] decimalBuf; // mutable - one per instance private static final int DECIMAL_BUF_SIZE = 10; // sufficient for int numbers /* * Lightweight zero padding integer number format function. * * Note: This implementation is almost equivalent to format method in DateNumberFormat. * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat, * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative * date format test case, having local implementation is ~10% faster than using one in * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference. * * -Yoshito */ private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) { int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits; int index = limit - 1; while (true) { decimalBuf[index] = decDigits[(value % 10)]; value /= 10; if (index == 0 || value == 0) { break; } index--; } int padding = minDigits - (limit - index); while (padding > 0 && index > 0) { decimalBuf[--index] = decDigits[0]; padding--; } while (padding > 0) { // when pattern width is longer than decimalBuf, need extra // leading zeros - ticke#7595 buf.append(decDigits[0]); padding--; } buf.append(decimalBuf, index, limit - index); } /** * Formats a number with the specified minimum and maximum number of digits. */ protected String zeroPaddingNumber(long value, int minDigits, int maxDigits) { numberFormat.setMinimumIntegerDigits(minDigits); numberFormat.setMaximumIntegerDigits(maxDigits); return numberFormat.format(value); } /** * Format characters that indicate numeric fields always. */ private static final String NUMERIC_FORMAT_CHARS = ""ADdFgHhKkmrSsuWwYy""; /** * Format characters that indicate numeric fields when pattern lengh * is up to 2. */ private static final String NUMERIC_FORMAT_CHARS2 = ""ceLMQq""; /** * Return true if the given format character, occuring count * times, represents a numeric field. */ private static final boolean isNumeric(char formatChar, int count) { return NUMERIC_FORMAT_CHARS.indexOf(formatChar) >= 0 || (count <= 2 && NUMERIC_FORMAT_CHARS2.indexOf(formatChar) >= 0); } /** * Overrides DateFormat * @see DateFormat */ @Override public void parse(String text, Calendar cal, ParsePosition parsePos) { TimeZone backupTZ = null; Calendar resultCal = null; if (cal != calendar && !cal.getType().equals(calendar.getType())) { // Different calendar type // We use the time/zone from the input calendar, but // do not use the input calendar for field calculation. calendar.setTimeInMillis(cal.getTimeInMillis()); backupTZ = calendar.getTimeZone(); calendar.setTimeZone(cal.getTimeZone()); resultCal = cal; cal = calendar; } int pos = parsePos.getIndex(); if(pos < 0) { parsePos.setErrorIndex(0); return; } int start = pos; // Hold the day period until everything else is parsed, because we need // the hour to interpret time correctly. // Using an one-element array for output parameter. Output dayPeriod = new Output(null); Output tzTimeType = new Output(TimeType.UNKNOWN); boolean[] ambiguousYear = { false }; // item index for the first numeric field within a contiguous numeric run int numericFieldStart = -1; // item length for the first numeric field within a contiguous numeric run int numericFieldLength = 0; // start index of numeric text run in the input text int numericStartPos = 0; MessageFormat numericLeapMonthFormatter = null; if (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT) { numericLeapMonthFormatter = new MessageFormat(formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC], locale); } Object[] items = getPatternItems(); int i = 0; while (i < items.length) { if (items[i] instanceof PatternItem) { // Handle pattern field PatternItem field = (PatternItem)items[i]; if (field.isNumeric) { // Handle fields within a run of abutting numeric fields. Take // the pattern ""HHmmss"" as an example. We will try to parse // 2/2/2 characters of the input text, then if that fails, // 1/2/2. We only adjust the width of the leftmost field; the // others remain fixed. This allows ""123456"" => 12:34:56, but // ""12345"" => 1:23:45. Likewise, for the pattern ""yyyyMMdd"" we // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2. if (numericFieldStart == -1) { // check if this field is followed by abutting another numeric field if ((i + 1) < items.length && (items[i + 1] instanceof PatternItem) && ((PatternItem)items[i + 1]).isNumeric) { // record the first numeric field within a numeric text run numericFieldStart = i; numericFieldLength = field.length; numericStartPos = pos; } } } if (numericFieldStart != -1) { // Handle a numeric field within abutting numeric fields int len = field.length; if (numericFieldStart == i) { len = numericFieldLength; } // Parse a numeric field pos = subParse(text, pos, field.type, len, true, false, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType); if (pos < 0) { // If the parse fails anywhere in the numeric run, back up to the // start of the run and use shorter pattern length for the first // numeric field. --numericFieldLength; if (numericFieldLength == 0) { // can not make shorter any more parsePos.setIndex(start); parsePos.setErrorIndex(pos); if (backupTZ != null) { calendar.setTimeZone(backupTZ); } return; } i = numericFieldStart; pos = numericStartPos; continue; } } else if (field.type != 'l') { // (SMALL LETTER L) obsolete pattern char just gets ignored // Handle a non-numeric field or a non-abutting numeric field numericFieldStart = -1; int s = pos; pos = subParse(text, pos, field.type, field.length, false, true, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType, dayPeriod); if (pos < 0) { if (pos == ISOSpecialEra) { // era not present, in special cases allow this to continue pos = s; if (i+1 < items.length) { String patl = null; // if it will cause a class cast exception to String, we can't use it try { patl = (String)items[i+1]; } catch(ClassCastException cce) { parsePos.setIndex(start); parsePos.setErrorIndex(s); if (backupTZ != null) { calendar.setTimeZone(backupTZ); } return; } // get next item in pattern if(patl == null) patl = (String)items[i+1]; int plen = patl.length(); int idx=0; // White space characters found in pattern. // Skip contiguous white spaces. while (idx < plen) { char pch = patl.charAt(idx); if (PatternProps.isWhiteSpace(pch)) idx++; else break; } // if next item in pattern is all whitespace, skip it if (idx == plen) { i++; } } } else { parsePos.setIndex(start); parsePos.setErrorIndex(s); if (backupTZ != null) { calendar.setTimeZone(backupTZ); } return; } } } } else { // Handle literal pattern text literal numericFieldStart = -1; boolean[] complete = new boolean[1]; pos = matchLiteral(text, pos, items, i, complete); if (!complete[0]) { // Set the position of mismatch parsePos.setIndex(start); parsePos.setErrorIndex(pos); if (backupTZ != null) { calendar.setTimeZone(backupTZ); } return; } } ++i; } // Special hack for trailing ""."" after non-numeric field. if (pos < text.length()) { char extra = text.charAt(pos); if (extra == '.' && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && items.length != 0) { // only do if the last field is not numeric Object lastItem = items[items.length - 1]; if (lastItem instanceof PatternItem && !((PatternItem)lastItem).isNumeric) { pos++; // skip the extra ""."" } } } // If dayPeriod is set, use it in conjunction with hour-of-day to determine am/pm. if (dayPeriod.value != null) { DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale()); if (!cal.isSet(Calendar.HOUR) && !cal.isSet(Calendar.HOUR_OF_DAY)) { // If hour is not set, set time to the midpoint of current day period, overwriting // minutes if it's set. double midPoint = ruleSet.getMidPointForDayPeriod(dayPeriod.value); // Truncate midPoint toward zero to get the hour. // Any leftover means it was a half-hour. int midPointHour = (int) midPoint; int midPointMinute = (midPoint - midPointHour) > 0 ? 30 : 0; // No need to set am/pm because hour-of-day is set last therefore takes precedence. cal.set(Calendar.HOUR_OF_DAY, midPointHour); cal.set(Calendar.MINUTE, midPointMinute); } else { int hourOfDay; if (cal.isSet(Calendar.HOUR_OF_DAY)) { // Hour is parsed in 24-hour format. hourOfDay = cal.get(Calendar.HOUR_OF_DAY); } else { // Hour is parsed in 12-hour format. hourOfDay = cal.get(Calendar.HOUR); // cal.get() turns 12 to 0 for 12-hour time; change 0 to 12 // so 0 unambiguously means a 24-hour time from above. if (hourOfDay == 0) { hourOfDay = 12; } } assert(0 <= hourOfDay && hourOfDay <= 23); // If hour-of-day is 0 or 13 thru 23 then input time in unambiguously in 24-hour format. if (hourOfDay == 0 || (13 <= hourOfDay && hourOfDay <= 23)) { // Make hour-of-day take precedence over (hour + am/pm) by setting it again. cal.set(Calendar.HOUR_OF_DAY, hourOfDay); } else { // We have a 12-hour time and need to choose between am and pm. // Behave as if dayPeriod spanned 6 hours each way from its center point. // This will parse correctly for consistent time + period (e.g. 10 at night) as // well as provide a reasonable recovery for inconsistent time + period (e.g. // 9 in the afternoon). // Assume current time is in the AM. // - Change 12 back to 0 for easier handling of 12am. // - Append minutes as fractional hours because e.g. 8:15 and 8:45 could be parsed // into different half-days if center of dayPeriod is at 14:30. // - cal.get(MINUTE) will return 0 if MINUTE is unset, which works. if (hourOfDay == 12) { hourOfDay = 0; } double currentHour = hourOfDay + cal.get(Calendar.MINUTE) / 60.0; double midPointHour = ruleSet.getMidPointForDayPeriod(dayPeriod.value); double hoursAheadMidPoint = currentHour - midPointHour; // Assume current time is in the AM. if (-6 <= hoursAheadMidPoint && hoursAheadMidPoint < 6) { // Assumption holds; set time as such. cal.set(Calendar.AM_PM, 0); } else { cal.set(Calendar.AM_PM, 1); } } } } // At this point the fields of Calendar have been set. Calendar // will fill in default values for missing fields when the time // is computed. parsePos.setIndex(pos); // This part is a problem: When we call parsedDate.after, we compute the time. // Take the date April 3 2004 at 2:30 am. When this is first set up, the year // will be wrong if we're parsing a 2-digit year pattern. It will be 1904. // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am // is therefore an ""impossible"" time, since the time goes from 1:59 to 3:00 am // on that day. It is therefore parsed out to fields as 3:30 am. Then we // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is // a Saturday, so it can have a 2:30 am -- and it should. [LIU] /* Date parsedDate = cal.getTime(); if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) { cal.add(Calendar.YEAR, 100); parsedDate = cal.getTime(); } */ // Because of the above condition, save off the fields in case we need to readjust. // The procedure we use here is not particularly efficient, but there is no other // way to do this given the API restrictions present in Calendar. We minimize // inefficiency by only performing this computation when it might apply, that is, // when the two-digit year is equal to the start year, and thus might fall at the // front or the back of the default century. This only works because we adjust // the year correctly to start with in other cases -- see subParse(). try { TimeType tztype = tzTimeType.value; if (ambiguousYear[0] || tztype != TimeType.UNKNOWN) { // We need a copy of the fields, and we need to avoid triggering a call to // complete(), which will recalculate the fields. Since we can't access // the fields[] array in Calendar, we clone the entire object. This will // stop working if Calendar.clone() is ever rewritten to call complete(). Calendar copy; if (ambiguousYear[0]) { // the two-digit year == the default start year copy = (Calendar)cal.clone(); Date parsedDate = copy.getTime(); if (parsedDate.before(getDefaultCenturyStart())) { // We can't use add here because that does a complete() first. cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100); } } if (tztype != TimeType.UNKNOWN) { copy = (Calendar)cal.clone(); TimeZone tz = copy.getTimeZone(); BasicTimeZone btz = null; if (tz instanceof BasicTimeZone) { btz = (BasicTimeZone)tz; } // Get local millis copy.set(Calendar.ZONE_OFFSET, 0); copy.set(Calendar.DST_OFFSET, 0); long localMillis = copy.getTimeInMillis(); // Make sure parsed time zone type (Standard or Daylight) // matches the rule used by the parsed time zone. int[] offsets = new int[2]; if (btz != null) { if (tztype == TimeType.STANDARD) { btz.getOffsetFromLocal(localMillis, BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets); } else { btz.getOffsetFromLocal(localMillis, BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets); } } else { // No good way to resolve ambiguous time at transition, // but following code work in most case. tz.getOffset(localMillis, true, offsets); if (tztype == TimeType.STANDARD && offsets[1] != 0 || tztype == TimeType.DAYLIGHT && offsets[1] == 0) { // Roll back one day and try it again. // Note: This code assumes 1. timezone transition only happens // once within 24 hours at max // 2. the difference of local offsets at the transition is // less than 24 hours. tz.getOffset(localMillis - (24*60*60*1000), true, offsets); } } // Now, compare the results with parsed type, either standard or // daylight saving time int resolvedSavings = offsets[1]; if (tztype == TimeType.STANDARD) { if (offsets[1] != 0) { // Override DST_OFFSET = 0 in the result calendar resolvedSavings = 0; } } else { // tztype == TZTYPE_DST if (offsets[1] == 0) { if (btz != null) { long time = localMillis + offsets[0]; // We use the nearest daylight saving time rule. TimeZoneTransition beforeTrs, afterTrs; long beforeT = time, afterT = time; int beforeSav = 0, afterSav = 0; // Search for DST rule before or on the time while (true) { beforeTrs = btz.getPreviousTransition(beforeT, true); if (beforeTrs == null) { break; } beforeT = beforeTrs.getTime() - 1; beforeSav = beforeTrs.getFrom().getDSTSavings(); if (beforeSav != 0) { break; } } // Search for DST rule after the time while (true) { afterTrs = btz.getNextTransition(afterT, false); if (afterTrs == null) { break; } afterT = afterTrs.getTime(); afterSav = afterTrs.getTo().getDSTSavings(); if (afterSav != 0) { break; } } if (beforeTrs != null && afterTrs != null) { if (time - beforeT > afterT - time) { resolvedSavings = afterSav; } else { resolvedSavings = beforeSav; } } else if (beforeTrs != null && beforeSav != 0) { resolvedSavings = beforeSav; } else if (afterTrs != null && afterSav != 0) { resolvedSavings = afterSav; } else { resolvedSavings = btz.getDSTSavings(); } } else { resolvedSavings = tz.getDSTSavings(); } if (resolvedSavings == 0) { // Final fallback resolvedSavings = millisPerHour; } } } cal.set(Calendar.ZONE_OFFSET, offsets[0]); cal.set(Calendar.DST_OFFSET, resolvedSavings); } } } // An IllegalArgumentException will be thrown by Calendar.getTime() // if any fields are out of range, e.g., MONTH == 17. catch (IllegalArgumentException e) { parsePos.setErrorIndex(pos); parsePos.setIndex(start); if (backupTZ != null) { calendar.setTimeZone(backupTZ); } return; } // Set the parsed result if local calendar is used // instead of the input calendar if (resultCal != null) { resultCal.setTimeZone(cal.getTimeZone()); resultCal.setTimeInMillis(cal.getTimeInMillis()); } // Restore the original time zone if required if (backupTZ != null) { calendar.setTimeZone(backupTZ); } } /** * Matches text (starting at pos) with patl. Returns the new pos, and sets complete[0] * if it matched the entire text. Whitespace sequences are treated as singletons. *

If isLenient and if we fail to match the first time, some special hacks are put into place. *

  • we are between date and time fields, then one or more whitespace characters * in the text are accepted instead.
  • *
    • we are after a non-numeric field, and the text starts with a ""."", we skip it.
    • *
    */ private int matchLiteral(String text, int pos, Object[] items, int itemIndex, boolean[] complete) { int originalPos = pos; String patternLiteral = (String)items[itemIndex]; int plen = patternLiteral.length(); int tlen = text.length(); int idx = 0; while (idx < plen && pos < tlen) { char pch = patternLiteral.charAt(idx); char ich = text.charAt(pos); if (PatternProps.isWhiteSpace(pch) && PatternProps.isWhiteSpace(ich)) { // White space characters found in both patten and input. // Skip contiguous white spaces. while ((idx + 1) < plen && PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1))) { ++idx; } while ((pos + 1) < tlen && PatternProps.isWhiteSpace(text.charAt(pos + 1))) { ++pos; } } else if (pch != ich) { if (ich == '.' && pos == originalPos && 0 < itemIndex && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) { Object before = items[itemIndex-1]; if (before instanceof PatternItem) { boolean isNumeric = ((PatternItem) before).isNumeric; if (!isNumeric) { ++pos; // just update pos continue; } } } else if ((pch == ' ' || pch == '.') && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) { ++idx; continue; } else if (pos != originalPos && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH)) { ++idx; continue; } break; } ++idx; ++pos; } complete[0] = idx == plen; if (complete[0] == false && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && 0 < itemIndex && itemIndex < items.length - 1) { // If fully lenient, accept "" ""* for any text between a date and a time field // We don't go more lenient, because we don't want to accept ""12/31"" for ""12:31"". // People may be trying to parse for a date, then for a time. if (originalPos < tlen) { Object before = items[itemIndex-1]; Object after = items[itemIndex+1]; if (before instanceof PatternItem && after instanceof PatternItem) { char beforeType = ((PatternItem) before).type; char afterType = ((PatternItem) after).type; if (DATE_PATTERN_TYPE.contains(beforeType) != DATE_PATTERN_TYPE.contains(afterType)) { int newPos = originalPos; while (true) { char ich = text.charAt(newPos); if (!PatternProps.isWhiteSpace(ich)) { break; } ++newPos; } complete[0] = newPos > originalPos; pos = newPos; } } } } return pos; } static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet(""[GyYuUQqMLlwWd]"").freeze(); /** * Attempt to match the text at a given position against an array of * strings. Since multiple strings in the array may match (for * example, if the array contains ""a"", ""ab"", and ""abc"", all will match * the input string ""abcd"") the longest match is returned. As a side * effect, the given field of cal is set to the index * of the best match, if there is one. * @param text the time text being parsed. * @param start where to start parsing. * @param field the date field being parsed. * @param data the string array to parsed. * @param cal * @return the new start position if matching succeeded; a negative * number indicating matching failure, otherwise. As a side effect, * sets the cal field field to the index * of the best match, if matching succeeded. */ protected int matchString(String text, int start, int field, String[] data, Calendar cal) { return matchString(text, start, field, data, null, cal); } /** * Attempt to match the text at a given position against an array of * strings. Since multiple strings in the array may match (for * example, if the array contains ""a"", ""ab"", and ""abc"", all will match * the input string ""abcd"") the longest match is returned. As a side * effect, the given field of cal is set to the index * of the best match, if there is one. * @param text the time text being parsed. * @param start where to start parsing. * @param field the date field being parsed. * @param data the string array to parsed. * @param monthPattern leap month pattern, or null if none. * @param cal * @return the new start position if matching succeeded; a negative * number indicating matching failure, otherwise. As a side effect, * sets the cal field field to the index * of the best match, if matching succeeded. * @deprecated This API is ICU internal only. * @hide draft / provisional / internal are hidden on Android */ @Deprecated private int matchString(String text, int start, int field, String[] data, String monthPattern, Calendar cal) { int i = 0; int count = data.length; if (field == Calendar.DAY_OF_WEEK) i = 1; // There may be multiple strings in the data[] array which begin with // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). // We keep track of the longest match, and return that. Note that this // unfortunately requires us to test all array elements. int bestMatchLength = 0, bestMatch = -1; int isLeapMonth = 0; int matchLength = 0; for (; i bestMatchLength && (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) { bestMatch = i; bestMatchLength = matchLength; isLeapMonth = 0; } if (monthPattern != null) { String leapMonthName = SimpleFormatterImpl.formatRawPattern( monthPattern, 1, 1, data[i]); length = leapMonthName.length(); if (length > bestMatchLength && (matchLength = regionMatchesWithOptionalDot(text, start, leapMonthName, length)) >= 0) { bestMatch = i; bestMatchLength = matchLength; isLeapMonth = 1; } } } if (bestMatch >= 0) { if (field >= 0) { if (field == Calendar.YEAR) { bestMatch++; // only get here for cyclic year names, which match 1-based years 1-60 } cal.set(field, bestMatch); if (monthPattern != null) { cal.set(Calendar.IS_LEAP_MONTH, isLeapMonth); } } return start + bestMatchLength; } return ~start; } private int regionMatchesWithOptionalDot(String text, int start, String data, int length) { boolean matches = text.regionMatches(true, start, data, 0, length); if (matches) { return length; } if (data.length() > 0 && data.charAt(data.length()-1) == '.') { if (text.regionMatches(true, start, data, 0, length-1)) { return length - 1; } } return -1; } /** * Attempt to match the text at a given position against an array of quarter * strings. Since multiple strings in the array may match (for * example, if the array contains ""a"", ""ab"", and ""abc"", all will match * the input string ""abcd"") the longest match is returned. As a side * effect, the given field of cal is set to the index * of the best match, if there is one. * @param text the time text being parsed. * @param start where to start parsing. * @param field the date field being parsed. * @param data the string array to parsed. * @return the new start position if matching succeeded; a negative * number indicating matching failure, otherwise. As a side effect, * sets the cal field field to the index * of the best match, if matching succeeded. */ protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal) { int i = 0; int count = data.length; // There may be multiple strings in the data[] array which begin with // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). // We keep track of the longest match, and return that. Note that this // unfortunately requires us to test all array elements. int bestMatchLength = 0, bestMatch = -1; int matchLength = 0; for (; i bestMatchLength && (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) { bestMatch = i; bestMatchLength = matchLength; } } if (bestMatch >= 0) { cal.set(field, bestMatch * 3); return start + bestMatchLength; } return -start; } /** * Similar to matchQuarterString but customized for day periods. */ private int matchDayPeriodString(String text, int start, String[] data, int dataLength, Output dayPeriod) { int bestMatchLength = 0, bestMatch = -1; int matchLength = 0; for (int i = 0; i < dataLength; ++i) { // Only try matching if the string exists. if (data[i] != null) { int length = data[i].length(); if (length > bestMatchLength && (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) { bestMatch = i; bestMatchLength = matchLength; } } } if (bestMatch >= 0) { dayPeriod.value = DayPeriodRules.DayPeriod.VALUES[bestMatch]; return start + bestMatchLength; } return -start; } /** * Protected method that converts one field of the input string into a * numeric field value in cal. Returns -start (for * ParsePosition) if failed. Subclasses may override this method to * modify or add parsing capabilities. * @param text the time text to be parsed. * @param start where to start parsing. * @param ch the pattern character for the date field text to be parsed. * @param count the count of a pattern character. * @param obeyCount if true, then the next field directly abuts this one, * and we should use the count to know when to stop parsing. * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] * is true, then a two-digit year was parsed and may need to be readjusted. * @param cal * @return the new start position if matching succeeded; a negative * number indicating matching failure, otherwise. As a side effect, * set the appropriate field of cal with the parsed * value. */ protected int subParse(String text, int start, char ch, int count, boolean obeyCount, boolean allowNegative, boolean[] ambiguousYear, Calendar cal) { return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null); } /** * Overloading to provide default argument (null) for day period. */ private int subParse(String text, int start, char ch, int count, boolean obeyCount, boolean allowNegative, boolean[] ambiguousYear, Calendar cal, MessageFormat numericLeapMonthFormatter, Output tzTimeType) { return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null, null); } /** * Protected method that converts one field of the input string into a * numeric field value in cal. Returns -start (for * ParsePosition) if failed. Subclasses may override this method to * modify or add parsing capabilities. * @param text the time text to be parsed. * @param start where to start parsing. * @param ch the pattern character for the date field text to be parsed. * @param count the count of a pattern character. * @param obeyCount if true, then the next field directly abuts this one, * and we should use the count to know when to stop parsing. * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] * is true, then a two-digit year was parsed and may need to be readjusted. * @param cal * @param numericLeapMonthFormatter if non-null, used to parse numeric leap months. * @param tzTimeType the type of parsed time zone - standard, daylight or unknown (output). * This parameter can be null if caller does not need the information. * @return the new start position if matching succeeded; a negative * number indicating matching failure, otherwise. As a side effect, * set the appropriate field of cal with the parsed * value. * @deprecated This API is ICU internal only. * @hide draft / provisional / internal are hidden on Android */ @Deprecated @SuppressWarnings(""fallthrough"") private int subParse(String text, int start, char ch, int count, boolean obeyCount, boolean allowNegative, boolean[] ambiguousYear, Calendar cal, MessageFormat numericLeapMonthFormatter, Output tzTimeType, Output dayPeriod) { Number number = null; NumberFormat currentNumberFormat = null; int value = 0; int i; ParsePosition pos = new ParsePosition(0); int patternCharIndex = getIndexFromChar(ch); if (patternCharIndex == -1) { return ~start; } currentNumberFormat = getNumberFormat(ch); int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; // -1 if irrelevant if (numericLeapMonthFormatter != null) { numericLeapMonthFormatter.setFormatByArgumentIndex(0, currentNumberFormat); } boolean isChineseCalendar = ( cal.getType().equals(""chinese"") || cal.getType().equals(""dangi"") ); // If there are any spaces here, skip over them. If we hit the end // of the string, then fail. for (;;) { if (start >= text.length()) { return ~start; } int c = UTF16.charAt(text, start); if (!UCharacter.isUWhiteSpace(c) || !PatternProps.isWhiteSpace(c)) { break; } start += UTF16.getCharCount(c); } pos.setIndex(start); // We handle a few special cases here where we need to parse // a number value. We handle further, more generic cases below. We need // to handle some of them here because some fields require extra processing on // the parsed value. if (patternCharIndex == 4 /*'k' HOUR_OF_DAY1_FIELD*/ || patternCharIndex == 15 /*'h' HOUR1_FIELD*/ || (patternCharIndex == 2 /*'M' MONTH_FIELD*/ && count <= 2) || patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ || patternCharIndex == 19 /*'e' DOW_LOCAL*/ || patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ || patternCharIndex == 1 /*'y' YEAR */ || patternCharIndex == 18 /*'Y' YEAR_WOY */ || patternCharIndex == 30 /*'U' YEAR_NAME_FIELD, falls back to numeric */ || (patternCharIndex == 0 /*'G' ERA */ && isChineseCalendar) || patternCharIndex == 27 /* 'Q' - QUARTER*/ || patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/ || patternCharIndex == 8 /*'S' FRACTIONAL_SECOND */ ) { // It would be good to unify this with the obeyCount logic below, // but that's going to be difficult. boolean parsedNumericLeapMonth = false; if (numericLeapMonthFormatter != null && (patternCharIndex == 2 || patternCharIndex == 26)) { // First see if we can parse month number with leap month pattern Object[] args = numericLeapMonthFormatter.parse(text, pos); if (args != null && pos.getIndex() > start && (args[0] instanceof Number)) { parsedNumericLeapMonth = true; number = (Number)args[0]; cal.set(Calendar.IS_LEAP_MONTH, 1); } else { pos.setIndex(start); cal.set(Calendar.IS_LEAP_MONTH, 0); } } if (!parsedNumericLeapMonth) { if (obeyCount) { if ((start+count) > text.length()) { return ~start; } number = parseInt(text, count, pos, allowNegative,currentNumberFormat); } else { number = parseInt(text, pos, allowNegative,currentNumberFormat); } if (number == null && !allowNumericFallback(patternCharIndex)) { // only return if pattern is NOT one that allows numeric fallback return ~start; } } if (number != null) { value = number.intValue(); } } switch (patternCharIndex) { case 0: // 'G' - ERA if ( isChineseCalendar ) { // Numeric era handling moved from ChineseDateFormat, // If we didn't have a number, already returned -start above cal.set(Calendar.ERA, value); return pos.getIndex(); } int ps = 0; if (count == 5) { ps = matchString(text, start, Calendar.ERA, formatData.narrowEras, null, cal); } else if (count == 4) { ps = matchString(text, start, Calendar.ERA, formatData.eraNames, null, cal); } else { ps = matchString(text, start, Calendar.ERA, formatData.eras, null, cal); } // check return position, if it equals -start, then matchString error // special case the return code so we don't necessarily fail out until we // verify no year information also if (ps == ~start) ps = ISOSpecialEra; return ps; case 1: // 'y' - YEAR case 18: // 'Y' - YEAR_WOY // If there are 3 or more YEAR pattern characters, this indicates // that the year value is to be treated literally, without any // two-digit year adjustments (e.g., from ""01"" to 2001). Otherwise // we made adjustments to place the 2-digit year in the proper // century, for parsed strings from ""00"" to ""99"". Any other string // is treated literally: ""2250"", ""-1"", ""1"", ""002"". /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/ /* Skip this for Chinese calendar, moved from ChineseDateFormat */ if ( override != null && (override.compareTo(""hebr"") == 0 || override.indexOf(""y=hebr"") >= 0) && value < 1000 ) { value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR; } else if (count == 2 && (pos.getIndex() - start) == 2 && cal.haveDefaultCentury() && UCharacter.isDigit(text.charAt(start)) && UCharacter.isDigit(text.charAt(start+1))) { // Assume for example that the defaultCenturyStart is 6/18/1903. // This means that two-digit years will be forced into the range // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond // to 1904, 1905, etc. If the year is 03, then it is 2003 if the // other fields specify a date before 6/18, or 1903 if they specify a // date afterwards. As a result, 03 is an ambiguous year. All other // two-digit years are unambiguous. int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100; ambiguousYear[0] = value == ambiguousTwoDigitYear; value += (getDefaultCenturyStartYear()/100)*100 + (value < ambiguousTwoDigitYear ? 100 : 0); } cal.set(field, value); // Delayed checking for adjustment of Hebrew month numbers in non-leap years. if (DelayedHebrewMonthCheck) { if (!HebrewCalendar.isLeapYear(value)) { cal.add(Calendar.MONTH,1); } DelayedHebrewMonthCheck = false; } return pos.getIndex(); case 30: // 'U' - YEAR_NAME_FIELD if (formatData.shortYearNames != null) { int newStart = matchString(text, start, Calendar.YEAR, formatData.shortYearNames, null, cal); if (newStart > 0) { return newStart; } } if ( number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC) || formatData.shortYearNames == null || value > formatData.shortYearNames.length) ) { cal.set(Calendar.YEAR, value); return pos.getIndex(); } return ~start; case 2: // 'M' - MONTH case 26: // 'L' - STAND_ALONE_MONTH if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { // i.e., M/MM, L/LL or lenient & have a number // Don't want to parse the month if it is a string // while pattern uses numeric style: M/MM, L/LL. // [We computed 'value' above.] cal.set(Calendar.MONTH, value - 1); // When parsing month numbers from the Hebrew Calendar, we might need // to adjust the month depending on whether or not it was a leap year. // We may or may not yet know what year it is, so might have to delay // checking until the year is parsed. if (cal.getType().equals(""hebrew"") && value >= 6) { if (cal.isSet(Calendar.YEAR)) { if (!HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR))) { cal.set(Calendar.MONTH, value); } } else { DelayedHebrewMonthCheck = true; } } return pos.getIndex(); } else { // count >= 3 // i.e., MMM/MMMM or LLL/LLLL // Want to be able to parse both short and long forms. boolean haveMonthPat = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT); // Try count == 4 first:, unless we're strict int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { newStart = (patternCharIndex == 2)? matchString(text, start, Calendar.MONTH, formatData.months, (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null, cal): matchString(text, start, Calendar.MONTH, formatData.standaloneMonths, (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null, cal); if (newStart > 0) { return newStart; } } // count == 4 failed, now try count == 3 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { return (patternCharIndex == 2)? matchString(text, start, Calendar.MONTH, formatData.shortMonths, (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null, cal): matchString(text, start, Calendar.MONTH, formatData.standaloneShortMonths, (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null, cal); } return newStart; } case 4: // 'k' - HOUR_OF_DAY (1..24) // [We computed 'value' above.] if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) { value = 0; } cal.set(Calendar.HOUR_OF_DAY, value); return pos.getIndex(); case 8: // 'S' - FRACTIONAL_SECOND // Fractional seconds left-justify i = pos.getIndex() - start; if (i < 3) { while (i < 3) { value *= 10; i++; } } else { int a = 1; while (i > 3) { a *= 10; i--; } value /= a; } cal.set(Calendar.MILLISECOND, value); return pos.getIndex(); case 19: // 'e' - DOW_LOCAL if(count <= 2 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { // i.e. e/ee or lenient and have a number cal.set(field, value); return pos.getIndex(); } // else for eee-eeeeee, fall through to EEE-EEEEEE handling //$FALL-THROUGH$ case 9: { // 'E' - DAY_OF_WEEK // Want to be able to parse at least wide, abbrev, short, and narrow forms. int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.weekdays, null, cal)) > 0) { // try EEEE wide return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shortWeekdays, null, cal)) > 0) { // try EEE abbrev return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) { if (formatData.shorterWeekdays != null) { if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shorterWeekdays, null, cal)) > 0) { // try EEEEEE short return newStart; } } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 5) { if (formatData.narrowWeekdays != null) { if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.narrowWeekdays, null, cal)) > 0) { // try EEEEE narrow return newStart; } } } return newStart; } case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK if(count == 1 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { // i.e. c or lenient and have a number cal.set(field, value); return pos.getIndex(); } // Want to be able to parse at least wide, abbrev, short forms. int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal)) > 0) { // try cccc wide return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShortWeekdays, null, cal)) > 0) { // try ccc abbrev return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) { if (formatData.standaloneShorterWeekdays != null) { return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShorterWeekdays, null, cal); // try cccccc short } } return newStart; } case 14: { // 'a' - AM_PM // Optionally try both wide/abbrev and narrow forms. // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version, // in which case our only option is wide form int newStart = 0; // try wide/abbrev a-aaaa if(formatData.ampmsNarrow == null || count < 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH)) { if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampms, null, cal)) > 0) { return newStart; } } // try narrow aaaaa if(formatData.ampmsNarrow != null && (count >= 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH))) { if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampmsNarrow, null, cal)) > 0) { return newStart; } } // no matches for given options return ~start; } case 15: // 'h' - HOUR (1..12) // [We computed 'value' above.] if (value == cal.getLeastMaximum(Calendar.HOUR)+1) { value = 0; } cal.set(Calendar.HOUR, value); return pos.getIndex(); case 17: // 'z' - ZONE_OFFSET { Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.SPECIFIC_LONG; TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); if (tz != null) { cal.setTimeZone(tz); return pos.getIndex(); } return ~start; } case 23: // 'Z' - TIMEZONE_RFC { Style style = (count < 4) ? Style.ISO_BASIC_LOCAL_FULL : ((count == 5) ? Style.ISO_EXTENDED_FULL : Style.LOCALIZED_GMT); TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); if (tz != null) { cal.setTimeZone(tz); return pos.getIndex(); } return ~start; } case 24: // 'v' - TIMEZONE_GENERIC { // Note: 'v' only supports count 1 and 4 Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG; TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); if (tz != null) { cal.setTimeZone(tz); return pos.getIndex(); } return ~start; } case 29: // 'V' - TIMEZONE_SPECIAL { Style style = null; switch (count) { case 1: style = Style.ZONE_ID_SHORT; break; case 2: style = Style.ZONE_ID; break; case 3: style = Style.EXEMPLAR_LOCATION; break; default: style = Style.GENERIC_LOCATION; break; } TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); if (tz != null) { cal.setTimeZone(tz); return pos.getIndex(); } return ~start; } case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET { Style style = (count < 4) ? Style.LOCALIZED_GMT_SHORT : Style.LOCALIZED_GMT; TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); if (tz != null) { cal.setTimeZone(tz); return pos.getIndex(); } return ~start; } case 32: // 'X' - TIMEZONE_ISO { Style style; switch (count) { case 1: style = Style.ISO_BASIC_SHORT; break; case 2: style = Style.ISO_BASIC_FIXED; break; case 3: style = Style.ISO_EXTENDED_FIXED; break; case 4: style = Style.ISO_BASIC_FULL; break; default: // count >= 5 style = Style.ISO_EXTENDED_FULL; break; } TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); if (tz != null) { cal.setTimeZone(tz); return pos.getIndex(); } return ~start; } case 33: // 'x' - TIMEZONE_ISO_LOCAL { Style style; switch (count) { case 1: style = Style.ISO_BASIC_LOCAL_SHORT; break; case 2: style = Style.ISO_BASIC_LOCAL_FIXED; break; case 3: style = Style.ISO_EXTENDED_LOCAL_FIXED; break; case 4: style = Style.ISO_BASIC_LOCAL_FULL; break; default: // count >= 5 style = Style.ISO_EXTENDED_LOCAL_FULL; break; } TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); if (tz != null) { cal.setTimeZone(tz); return pos.getIndex(); } return ~start; } case 27: // 'Q' - QUARTER if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { // i.e., Q or QQ. or lenient & have number // Don't want to parse the quarter if it is a string // while pattern uses numeric style: Q or QQ. // [We computed 'value' above.] cal.set(Calendar.MONTH, (value - 1) * 3); return pos.getIndex(); } else { // count >= 3 // i.e., QQQ or QQQQ // Want to be able to parse both short and long forms. // Try count == 4 first: int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.quarters, cal)) > 0) { return newStart; } } // count == 4 failed, now try count == 3 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { return matchQuarterString(text, start, Calendar.MONTH, formatData.shortQuarters, cal); } return newStart; } case 28: // 'q' - STANDALONE QUARTER if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { // i.e., q or qq. or lenient & have number // Don't want to parse the quarter if it is a string // while pattern uses numeric style: q or qq. // [We computed 'value' above.] cal.set(Calendar.MONTH, (value - 1) * 3); return pos.getIndex(); } else { // count >= 3 // i.e., qqq or qqqq // Want to be able to parse both short and long forms. // Try count == 4 first: int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.standaloneQuarters, cal)) > 0) { return newStart; } } // count == 4 failed, now try count == 3 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { return matchQuarterString(text, start, Calendar.MONTH, formatData.standaloneShortQuarters, cal); } return newStart; } case 37: // TIME SEPARATOR (no pattern character currently defined, we should // not get here but leave support in for future definition. { // Try matching a time separator. ArrayList data = new ArrayList(3); data.add(formatData.getTimeSeparatorString()); // Add the default, if different from the locale. if (!formatData.getTimeSeparatorString().equals(DateFormatSymbols.DEFAULT_TIME_SEPARATOR)) { data.add(DateFormatSymbols.DEFAULT_TIME_SEPARATOR); } // If lenient, add also the alternate, if different from the locale. if (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH) && !formatData.getTimeSeparatorString().equals(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR)) { data.add(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR); } return matchString(text, start, -1 /* => nothing to set */, data.toArray(new String[0]), cal); } case 35: // 'b' -- fixed day period (am/pm/midnight/noon) { int ampmStart = subParse(text, start, 'a', count, obeyCount, allowNegative, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType, dayPeriod); if (ampmStart > 0) { return ampmStart; } else { int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { if ((newStart = matchDayPeriodString( text, start, formatData.abbreviatedDayPeriods, 2, dayPeriod)) > 0) { return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchDayPeriodString( text, start, formatData.wideDayPeriods, 2, dayPeriod)) > 0) { return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchDayPeriodString( text, start, formatData.narrowDayPeriods, 2, dayPeriod)) > 0) { return newStart; } } return newStart; } } case 36: // 'B' -- flexible day period { int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { if ((newStart = matchDayPeriodString( text, start, formatData.abbreviatedDayPeriods, formatData.abbreviatedDayPeriods.length, dayPeriod)) > 0) { return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchDayPeriodString( text, start, formatData.wideDayPeriods, formatData.wideDayPeriods.length, dayPeriod)) > 0) { return newStart; } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchDayPeriodString( text, start, formatData.narrowDayPeriods, formatData.narrowDayPeriods.length, dayPeriod)) > 0) { return newStart; } } return newStart; } default: // case 3: // 'd' - DATE // case 5: // 'H' - HOUR_OF_DAY (0..23) // case 6: // 'm' - MINUTE // case 7: // 's' - SECOND // case 10: // 'D' - DAY_OF_YEAR // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH // case 12: // 'w' - WEEK_OF_YEAR // case 13: // 'W' - WEEK_OF_MONTH // case 16: // 'K' - HOUR (0..11) // case 20: // 'u' - EXTENDED_YEAR // case 21: // 'g' - JULIAN_DAY // case 22: // 'A' - MILLISECONDS_IN_DAY // case 34: // // Handle ""generic"" fields if (obeyCount) { if ((start+count) > text.length()) return -start; number = parseInt(text, count, pos, allowNegative,currentNumberFormat); } else { number = parseInt(text, pos, allowNegative,currentNumberFormat); } if (number != null) { if (patternCharIndex != DateFormat.RELATED_YEAR) { cal.set(field, number.intValue()); } else { cal.setRelatedYear(number.intValue()); } return pos.getIndex(); } return ~start; } } /** * return true if the pattern specified by patternCharIndex is one that allows * numeric fallback regardless of actual pattern size. */ private boolean allowNumericFallback(int patternCharIndex) { if (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ || patternCharIndex == 19 /*'e' DOW_LOCAL*/ || patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ || patternCharIndex == 30 /*'U' YEAR_NAME_FIELD*/ || patternCharIndex == 27 /* 'Q' - QUARTER*/ || patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/) { return true; } return false; } /** * Parse an integer using numberFormat. This method is semantically * const, but actually may modify fNumberFormat. */ private Number parseInt(String text, ParsePosition pos, boolean allowNegative, NumberFormat fmt) { return parseInt(text, -1, pos, allowNegative, fmt); } /** * Parse an integer using numberFormat up to maxDigits. */ private Number parseInt(String text, int maxDigits, ParsePosition pos, boolean allowNegative, NumberFormat fmt) { Number number; int oldPos = pos.getIndex(); if (allowNegative) { number = fmt.parse(text, pos); } else { // Invalidate negative numbers if (fmt instanceof DecimalFormat) { String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix(); ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX); number = fmt.parse(text, pos); ((DecimalFormat)fmt).setNegativePrefix(oldPrefix); } else { boolean dateNumberFormat = (fmt instanceof DateNumberFormat); if (dateNumberFormat) { ((DateNumberFormat)fmt).setParsePositiveOnly(true); } number = fmt.parse(text, pos); if (dateNumberFormat) { ((DateNumberFormat)fmt).setParsePositiveOnly(false); } } } if (maxDigits > 0) { // adjust the result to fit into // the maxDigits and move the position back int nDigits = pos.getIndex() - oldPos; if (nDigits > maxDigits) { double val = number.doubleValue(); nDigits -= maxDigits; while (nDigits > 0) { val /= 10; nDigits--; } pos.setIndex(oldPos + maxDigits); number = Integer.valueOf((int)val); } } return number; } /** * Translate a pattern, mapping each character in the from string to the * corresponding character in the to string. */ private String translatePattern(String pat, String from, String to) { StringBuilder result = new StringBuilder(); boolean inQuote = false; for (int i = 0; i < pat.length(); ++i) { char c = pat.charAt(i); if (inQuote) { if (c == '\'') inQuote = false; } else { if (c == '\'') { inQuote = true; } else if (isSyntaxChar(c)) { int ci = from.indexOf(c); if (ci != -1) { c = to.charAt(ci); } // do not worry on translatepattern if the character is not listed // we do the validity check elsewhere } } result.append(c); } if (inQuote) { throw new IllegalArgumentException(""Unfinished quote in pattern""); } return result.toString(); } /** * Return a pattern string describing this date format. */ public String toPattern() { return pattern; } /** * Return a localized pattern string describing this date format. *

    * Note: This implementation depends on {@link DateFormatSymbols#getLocalPatternChars()} * to get localized format pattern characters. ICU does not include * localized pattern character data, therefore, unless user sets localized * pattern characters manually, this method returns the same result as * {@link #toPattern()}. */ public String toLocalizedPattern() { return translatePattern(pattern, DateFormatSymbols.patternChars, formatData.localPatternChars); } /** * Apply the given unlocalized pattern string to this date format. */ public void applyPattern(String pat) { this.pattern = pat; parsePattern(); setLocale(null, null); // reset parsed pattern items patternItems = null; } /** * Apply the given localized pattern string to this date format. */ public void applyLocalizedPattern(String pat) { this.pattern = translatePattern(pat, formatData.localPatternChars, DateFormatSymbols.patternChars); setLocale(null, null); } /** * Gets the date/time formatting data. * @return a copy of the date-time formatting data associated * with this date-time formatter. */ public DateFormatSymbols getDateFormatSymbols() { return (DateFormatSymbols)formatData.clone(); } /** * Allows you to set the date/time formatting data. * @param newFormatSymbols the new symbols */ public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) { this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); } /** * Method for subclasses to access the DateFormatSymbols. */ protected DateFormatSymbols getSymbols() { return formatData; } /** * [icu] Gets the time zone formatter which this date/time * formatter uses to format and parse a time zone. * * @return the time zone formatter which this date/time * formatter uses. */ public TimeZoneFormat getTimeZoneFormat() { return tzFormat().freeze(); } /** * [icu] Allows you to set the time zone formatter. * * @param tzfmt the new time zone formatter */ public void setTimeZoneFormat(TimeZoneFormat tzfmt) { if (tzfmt.isFrozen()) { // If frozen, use it as is. tzFormat = tzfmt; } else { // If not frozen, clone and freeze. tzFormat = tzfmt.cloneAsThawed().freeze(); } } /** * Overrides Cloneable */ @Override public Object clone() { SimpleDateFormat other = (SimpleDateFormat) super.clone(); other.formatData = (DateFormatSymbols) formatData.clone(); // We must create a new copy of work buffer used by // the fast numeric field format code. if (this.decimalBuf != null) { other.decimalBuf = new char[DECIMAL_BUF_SIZE]; } return other; } /** * Override hashCode. * Generates the hash code for the SimpleDateFormat object */ @Override public int hashCode() { return pattern.hashCode(); // just enough fields for a reasonable distribution } /** * Override equals. */ @Override public boolean equals(Object obj) { if (!super.equals(obj)) return false; // super does class check SimpleDateFormat that = (SimpleDateFormat) obj; return (pattern.equals(that.pattern) && formatData.equals(that.formatData)); } /** * Override writeObject. * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html */ private void writeObject(ObjectOutputStream stream) throws IOException{ if (defaultCenturyStart == null) { // if defaultCenturyStart is not yet initialized, // calculate and set value before serialization. initializeDefaultCenturyStart(defaultCenturyBase); } initializeTimeZoneFormat(false); stream.defaultWriteObject(); stream.writeInt(getContext(DisplayContext.Type.CAPITALIZATION).value()); } /** * Override readObject. * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectInputStream.html */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int capitalizationSettingValue = (serialVersionOnStream > 1)? stream.readInt(): -1; ///CLOVER:OFF // don't have old serial data to test with if (serialVersionOnStream < 1) { // didn't have defaultCenturyStart field defaultCenturyBase = System.currentTimeMillis(); } ///CLOVER:ON else { // fill in dependent transient field parseAmbiguousDatesAsAfter(defaultCenturyStart); } serialVersionOnStream = currentSerialVersion; locale = getLocale(ULocale.VALID_LOCALE); if (locale == null) { // ICU4J 3.6 or older versions did not have UFormat locales // in the serialized data. This is just for preventing the // worst case scenario... locale = ULocale.getDefault(Category.FORMAT); } initLocalZeroPaddingNumberFormat(); setContext(DisplayContext.CAPITALIZATION_NONE); if (capitalizationSettingValue >= 0) { for (DisplayContext context: DisplayContext.values()) { if (context.value() == capitalizationSettingValue) { setContext(context); break; } } } // if serialized pre-56 update & turned off partial match switch to new enum value if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH) == false) { setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH, false); } parsePattern(); } /** * Format the object to an attributed string, and return the corresponding iterator * Overrides superclass method. * * @param obj The object to format * @return AttributedCharacterIterator describing the formatted value. */ @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { Calendar cal = calendar; if (obj instanceof Calendar) { cal = (Calendar)obj; } else if (obj instanceof Date) { calendar.setTime((Date)obj); } else if (obj instanceof Number) { calendar.setTimeInMillis(((Number)obj).longValue()); } else { throw new IllegalArgumentException(""Cannot format given Object as a Date""); } StringBuffer toAppendTo = new StringBuffer(); FieldPosition pos = new FieldPosition(0); List attributes = new ArrayList(); format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, attributes); AttributedString as = new AttributedString(toAppendTo.toString()); // add DateFormat field attributes to the AttributedString for (int i = 0; i < attributes.size(); i++) { FieldPosition fp = attributes.get(i); Format.Field attribute = fp.getFieldAttribute(); as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex()); } // return the CharacterIterator from AttributedString return as.getIterator(); } /** * Get the locale of this simple date formatter. * It is package accessible. also used in DateIntervalFormat. * * @return locale in this simple date formatter */ ULocale getLocale() { return locale; } /** * Check whether the 'field' is smaller than all the fields covered in * pattern, return true if it is. * The sequence of calendar field, * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,... * @param field the calendar field need to check against * @return true if the 'field' is smaller than all the fields * covered in pattern. false otherwise. */ boolean isFieldUnitIgnored(int field) { return isFieldUnitIgnored(pattern, field); } /* * Check whether the 'field' is smaller than all the fields covered in * pattern, return true if it is. * The sequence of calendar field, * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,... * @param pattern the pattern to check against * @param field the calendar field need to check against * @return true if the 'field' is smaller than all the fields * covered in pattern. false otherwise. */ static boolean isFieldUnitIgnored(String pattern, int field) { int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field]; int level; char ch; boolean inQuote = false; char prevCh = 0; int count = 0; for (int i = 0; i < pattern.length(); ++i) { ch = pattern.charAt(i); if (ch != prevCh && count > 0) { level = getLevelFromChar(prevCh); if (fieldLevel <= level) { return false; } count = 0; } if (ch == '\'') { if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') { ++i; } else { inQuote = ! inQuote; } } else if (!inQuote && isSyntaxChar(ch)) { prevCh = ch; ++count; } } if (count > 0) { // last item level = getLevelFromChar(prevCh); if (fieldLevel <= level) { return false; } } return true; } /** * Format date interval by algorithm. * It is supposed to be used only by CLDR survey tool. * * @param fromCalendar calendar set to the from date in date interval * to be formatted into date interval stirng * @param toCalendar calendar set to the to date in date interval * to be formatted into date interval stirng * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param pos On input: an alignment field, if desired. * On output: the offsets of the alignment field. * @exception IllegalArgumentException when there is non-recognized * pattern letter * @return Reference to 'appendTo' parameter. * @deprecated This API is ICU internal only. * @hide original deprecated declaration * @hide draft / provisional / internal are hidden on Android */ @Deprecated public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos) throws IllegalArgumentException { // not support different calendar types and time zones if ( !fromCalendar.isEquivalentTo(toCalendar) ) { throw new IllegalArgumentException(""can not format on two different calendars""); } Object[] items = getPatternItems(); int diffBegin = -1; int diffEnd = -1; /* look for different formatting string range */ // look for start of difference try { for (int i = 0; i < items.length; i++) { if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) { diffBegin = i; break; } } if ( diffBegin == -1 ) { // no difference, single date format return format(fromCalendar, appendTo, pos); } // look for end of difference for (int i = items.length-1; i >= diffBegin; i--) { if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) { diffEnd = i; break; } } } catch ( IllegalArgumentException e ) { throw new IllegalArgumentException(e.toString()); } // full range is different if ( diffBegin == 0 && diffEnd == items.length-1 ) { format(fromCalendar, appendTo, pos); appendTo.append("" \u2013 ""); // default separator format(toCalendar, appendTo, pos); return appendTo; } /* search for largest calendar field within the different range */ int highestLevel = 1000; for (int i = diffBegin; i <= diffEnd; i++) { if ( items[i] instanceof String) { continue; } PatternItem item = (PatternItem)items[i]; char ch = item.type; int patternCharIndex = getIndexFromChar(ch); if (patternCharIndex == -1) { throw new IllegalArgumentException(""Illegal pattern character "" + ""'"" + ch + ""' in \"""" + pattern + '""'); } if ( patternCharIndex < highestLevel ) { highestLevel = patternCharIndex; } } /* re-calculate diff range, including those calendar field which is in lower level than the largest calendar field covered in diff range calculated. */ try { for (int i = 0; i < diffBegin; i++) { if ( lowerLevel(items, i, highestLevel) ) { diffBegin = i; break; } } for (int i = items.length-1; i > diffEnd; i--) { if ( lowerLevel(items, i, highestLevel) ) { diffEnd = i; break; } } } catch ( IllegalArgumentException e ) { throw new IllegalArgumentException(e.toString()); } // full range is different if ( diffBegin == 0 && diffEnd == items.length-1 ) { format(fromCalendar, appendTo, pos); appendTo.append("" \u2013 ""); // default separator format(toCalendar, appendTo, pos); return appendTo; } // formatting // Initialize pos.setBeginIndex(0); pos.setEndIndex(0); DisplayContext capSetting = getContext(DisplayContext.Type.CAPITALIZATION); // formatting date 1 for (int i = 0; i <= diffEnd; i++) { if (items[i] instanceof String) { appendTo.append((String)items[i]); } else { PatternItem item = (PatternItem)items[i]; if (useFastFormat) { subFormat(appendTo, item.type, item.length, appendTo.length(), i, capSetting, pos, fromCalendar); } else { appendTo.append(subFormat(item.type, item.length, appendTo.length(), i, capSetting, pos, fromCalendar)); } } } appendTo.append("" \u2013 ""); // default separator // formatting date 2 for (int i = diffBegin; i < items.length; i++) { if (items[i] instanceof String) { appendTo.append((String)items[i]); } else { PatternItem item = (PatternItem)items[i]; if (useFastFormat) { subFormat(appendTo, item.type, item.length, appendTo.length(), i, capSetting, pos, toCalendar); } else { appendTo.append(subFormat(item.type, item.length, appendTo.length(), i, capSetting, pos, toCalendar)); } } } return appendTo; } /** * check whether the i-th item in 2 calendar is in different value. * * It is supposed to be used only by CLDR survey tool. * It is used by intervalFormatByAlgorithm(). * * @param fromCalendar one calendar * @param toCalendar the other calendar * @param items pattern items * @param i the i-th item in pattern items * @exception IllegalArgumentException when there is non-recognized * pattern letter * @return true is i-th item in 2 calendar is in different * value, false otherwise. */ private boolean diffCalFieldValue(Calendar fromCalendar, Calendar toCalendar, Object[] items, int i) throws IllegalArgumentException { if ( items[i] instanceof String) { return false; } PatternItem item = (PatternItem)items[i]; char ch = item.type; int patternCharIndex = getIndexFromChar(ch); if (patternCharIndex == -1) { throw new IllegalArgumentException(""Illegal pattern character "" + ""'"" + ch + ""' in \"""" + pattern + '""'); } final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; if (field >= 0) { int value = fromCalendar.get(field); int value_2 = toCalendar.get(field); if ( value != value_2 ) { return true; } } return false; } /** * check whether the i-th item's level is lower than the input 'level' * * It is supposed to be used only by CLDR survey tool. * It is used by intervalFormatByAlgorithm(). * * @param items the pattern items * @param i the i-th item in pattern items * @param level the level with which the i-th pattern item compared to * @exception IllegalArgumentException when there is non-recognized * pattern letter * @return true if i-th pattern item is lower than 'level', * false otherwise */ private boolean lowerLevel(Object[] items, int i, int level) throws IllegalArgumentException { if (items[i] instanceof String) { return false; } PatternItem item = (PatternItem)items[i]; char ch = item.type; int patternCharIndex = getLevelFromChar(ch); if (patternCharIndex == -1) { throw new IllegalArgumentException(""Illegal pattern character "" + ""'"" + ch + ""' in \"""" + pattern + '""'); } if (patternCharIndex >= level) { return true; } return false; } /** * allow the user to set the NumberFormat for several fields * It can be a single field like: ""y""(year) or ""M""(month) * It can be several field combined together: ""yMd""(year, month and date) * Note: * 1 symbol field is enough for multiple symbol fields (so ""y"" will override ""yy"", ""yyy"") * If the field is not numeric, then override has no effect (like ""MMM"" will use abbreviation, not numerical field) * * @param fields the fields to override * @param overrideNF the NumbeferFormat used * @exception IllegalArgumentException when the fields contain invalid field */ public void setNumberFormat(String fields, NumberFormat overrideNF) { overrideNF.setGroupingUsed(false); String nsName = ""$"" + UUID.randomUUID().toString(); // initialize mapping if not there if (numberFormatters == null) { numberFormatters = new HashMap(); } if (overrideMap == null) { overrideMap = new HashMap(); } // separate string into char and add to maps for (int i = 0; i < fields.length(); i++) { char field = fields.charAt(i); if (DateFormatSymbols.patternChars.indexOf(field) == -1) { throw new IllegalArgumentException(""Illegal field character "" + ""'"" + field + ""' in setNumberFormat.""); } overrideMap.put(field, nsName); numberFormatters.put(nsName, overrideNF); } // Since one or more of the override number formatters might be complex, // we can't rely on the fast numfmt where we have a partial field override. useLocalZeroPaddingNumberFormat = false; } /** * give the NumberFormat used for the field like 'y'(year) and 'M'(year) * * @param field the field the user wants * @return override NumberFormat used for the field */ public NumberFormat getNumberFormat(char field) { Character ovrField; ovrField = Character.valueOf(field); if (overrideMap != null && overrideMap.containsKey(ovrField)) { String nsName = overrideMap.get(ovrField).toString(); NumberFormat nf = numberFormatters.get(nsName); return nf; } else { return numberFormat; } } private void initNumberFormatters(ULocale loc) { numberFormatters = new HashMap(); overrideMap = new HashMap(); processOverrideString(loc,override); } private void processOverrideString(ULocale loc, String str) { if ( str == null || str.length() == 0 ) return; int start = 0; int end; String nsName; Character ovrField; boolean moreToProcess = true; boolean fullOverride; while (moreToProcess) { int delimiterPosition = str.indexOf("";"",start); if (delimiterPosition == -1) { moreToProcess = false; end = str.length(); } else { end = delimiterPosition; } String currentString = str.substring(start,end); int equalSignPosition = currentString.indexOf(""=""); if (equalSignPosition == -1) { // Simple override string such as ""hebrew"" nsName = currentString; fullOverride = true; } else { // Field specific override string such as ""y=hebrew"" nsName = currentString.substring(equalSignPosition+1); ovrField = Character.valueOf(currentString.charAt(0)); overrideMap.put(ovrField,nsName); fullOverride = false; } ULocale ovrLoc = new ULocale(loc.getBaseName()+""@numbers=""+nsName); NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE); nf.setGroupingUsed(false); if (fullOverride) { setNumberFormat(nf); } else { // Since one or more of the override number formatters might be complex, // we can't rely on the fast numfmt where we have a partial field override. useLocalZeroPaddingNumberFormat = false; } if (!fullOverride && !numberFormatters.containsKey(nsName)) { numberFormatters.put(nsName,nf); } start = delimiterPosition + 1; } } private void parsePattern() { hasMinute = false; hasSecond = false; boolean inQuote = false; for (int i = 0; i < pattern.length(); ++i) { char ch = pattern.charAt(i); if (ch == '\'') { inQuote = !inQuote; } if (!inQuote) { if (ch == 'm') { hasMinute = true; } if (ch == 's') { hasSecond = true; } } } } } ","itemLength " "// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.android; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ApkSigningMethod; import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.List; /** * A class for coordinating APK building, signing and zipaligning. * *

    It is not always necessary to zip align APKs, for instance if the APK does not contain * resources. Furthermore, we do not always care about the unsigned apk because it cannot be * installed on a device until it is signed. */ public class ApkActionsBuilder { private Artifact classesDex; private ImmutableList.Builder inputZips = new ImmutableList.Builder<>(); private Artifact javaResourceZip; private FilesToRunProvider resourceExtractor; private Artifact javaResourceFile; private NativeLibs nativeLibs = NativeLibs.EMPTY; private Artifact unsignedApk; private Artifact signedApk; private boolean zipalignApk = false; private List signingKeys; private Artifact signingLineage; private String artifactLocation; private Artifact v4SignatureFile; private boolean deterministicSigning; private String signingKeyRotationMinSdk; private final String apkName; public static ApkActionsBuilder create(String apkName) { return new ApkActionsBuilder(apkName); } private ApkActionsBuilder(String apkName) { this.apkName = apkName; } /** Sets the native libraries to be included in the APK. */ @CanIgnoreReturnValue public ApkActionsBuilder setNativeLibs(NativeLibs nativeLibs) { this.nativeLibs = nativeLibs; return this; } /** * Sets the dex file to be included in the APK. * *

    Can be either a plain classes.dex or a .zip file containing dexes. */ @CanIgnoreReturnValue public ApkActionsBuilder setClassesDex(Artifact classesDex) { Preconditions.checkArgument( classesDex == null || classesDex.getFilename().endsWith("".zip"") || classesDex.getFilename().equals(""classes.dex"")); this.classesDex = classesDex; return this; } /** Add a zip file that should be copied as is into the APK. */ @CanIgnoreReturnValue public ApkActionsBuilder addInputZip(Artifact inputZip) { this.inputZips.add(inputZip); return this; } @CanIgnoreReturnValue public ApkActionsBuilder addInputZips(Iterable inputZips) { this.inputZips.addAll(inputZips); return this; } /** * Adds a zip to be added to the APK and an executable that filters the zip to extract the * relevant contents first. */ @CanIgnoreReturnValue public ApkActionsBuilder setJavaResourceZip( Artifact javaResourceZip, FilesToRunProvider resourceExtractor) { this.javaResourceZip = javaResourceZip; this.resourceExtractor = resourceExtractor; return this; } /** * Adds an individual resource file to the root directory of the APK. * *

    This provides the same functionality as {@code javaResourceZip}, except much more hacky. * Will most probably won't work if there is an input artifact in the same directory as this file. */ @CanIgnoreReturnValue public ApkActionsBuilder setJavaResourceFile(Artifact javaResourceFile) { this.javaResourceFile = javaResourceFile; return this; } /** Requests an unsigned APK be built at the specified artifact. */ @CanIgnoreReturnValue public ApkActionsBuilder setUnsignedApk(Artifact unsignedApk) { this.unsignedApk = unsignedApk; return this; } /** Requests a signed APK be built at the specified artifact. */ @CanIgnoreReturnValue public ApkActionsBuilder setSignedApk(Artifact signedApk) { this.signedApk = signedApk; return this; } @CanIgnoreReturnValue public ApkActionsBuilder setV4Signature(Artifact v4SignatureFile) { this.v4SignatureFile = v4SignatureFile; return this; } /** Requests that signed APKs are zipaligned. */ @CanIgnoreReturnValue public ApkActionsBuilder setZipalignApk(boolean zipalign) { this.zipalignApk = zipalign; return this; } /** Sets the signing keys that will be used to sign the APK. */ @CanIgnoreReturnValue public ApkActionsBuilder setSigningKeys(List signingKeys) { this.signingKeys = signingKeys; return this; } /** Sets the signing lineage file used to sign the APK. */ @CanIgnoreReturnValue public ApkActionsBuilder setSigningLineageFile(Artifact signingLineage) { this.signingLineage = signingLineage; return this; } @CanIgnoreReturnValue public ApkActionsBuilder setSigningKeyRotationMinSdk(String minSdk) { this.signingKeyRotationMinSdk = minSdk; return this; } /** Sets the output APK instead of creating with a static/standard path. */ @CanIgnoreReturnValue public ApkActionsBuilder setArtifactLocationDirectory(String artifactLocation) { this.artifactLocation = artifactLocation; return this; } @CanIgnoreReturnValue public ApkActionsBuilder setDeterministicSigning(boolean deterministicSigning) { this.deterministicSigning = deterministicSigning; return this; } /** Registers the actions needed to build the requested APKs in the rule context. */ public void registerActions(RuleContext ruleContext) throws InterruptedException { // If the caller did not request an unsigned APK, we still need to construct one so that // we can sign it. So we make up an intermediate artifact. Artifact intermediateUnsignedApk = unsignedApk != null ? unsignedApk : getApkArtifact(ruleContext, ""unsigned_"" + signedApk.getFilename()); buildApk(ruleContext, intermediateUnsignedApk); if (signedApk != null) { Artifact apkToSign = intermediateUnsignedApk; // Zipalignment is performed before signing. So if a zipaligned APK is requested, we need an // intermediate zipaligned-but-not-signed apk artifact. if (zipalignApk) { apkToSign = getApkArtifact(ruleContext, ""zipaligned_"" + signedApk.getFilename()); zipalignApk(ruleContext, intermediateUnsignedApk, apkToSign); } signApk(ruleContext, apkToSign, signedApk); } } /** Appends the --output_jar_creator flag to the singlejar command line. */ private void setSingleJarCreatedBy(RuleContext ruleContext, CustomCommandLine.Builder builder) { if (ruleContext.getConfiguration().getFragment(BazelAndroidConfiguration.class) != null) { // Only enabled for Bazel, not Blaze. builder.add(""--output_jar_creator""); builder.add(""Bazel""); } } /** Registers generating actions for {@code outApk} that build an unsigned APK using SingleJar. */ private void buildApk(RuleContext ruleContext, Artifact outApk) throws InterruptedException { Artifact compressedApk = getApkArtifact(ruleContext, ""compressed_"" + outApk.getFilename()); SpawnAction.Builder compressedApkActionBuilder = createSpawnActionBuilder(ruleContext) .setMnemonic(""ApkBuilder"") .setProgressMessage(""Generating unsigned %s"", apkName) .addOutput(compressedApk); CustomCommandLine.Builder compressedApkCommandLine = CustomCommandLine.builder() .add(""--exclude_build_data"") .add(""--compression"") .add(""--normalize"") .addExecPath(""--output"", compressedApk); setSingleJarCreatedBy(ruleContext, compressedApkCommandLine); setSingleJarAsExecutable(ruleContext, compressedApkActionBuilder); if (classesDex != null) { compressedApkActionBuilder.addInput(classesDex); if (classesDex.getFilename().endsWith("".zip"")) { compressedApkCommandLine.addExecPath(""--sources"", classesDex); } else { compressedApkCommandLine .add(""--resources"") .addFormatted(""%s:%s"", classesDex, classesDex.getFilename()); } } if (javaResourceFile != null) { compressedApkActionBuilder.addInput(javaResourceFile); compressedApkCommandLine .add(""--resources"") .addFormatted(""%s:%s"", javaResourceFile, javaResourceFile.getFilename()); } for (String architecture : nativeLibs.getMap().keySet()) { for (Artifact nativeLib : nativeLibs.getMap().get(architecture).toList()) { compressedApkActionBuilder.addInput(nativeLib); compressedApkCommandLine .add(""--resources"") .addFormatted(""%s:lib/%s/%s"", nativeLib, architecture, nativeLib.getFilename()); } } SpawnAction.Builder singleJarActionBuilder = createSpawnActionBuilder(ruleContext) .setMnemonic(""ApkBuilder"") .setProgressMessage(""Generating unsigned %s"", apkName) .addInput(compressedApk) .addOutput(outApk); CustomCommandLine.Builder singleJarCommandLine = CustomCommandLine.builder(); singleJarCommandLine .add(""--exclude_build_data"") .add(""--dont_change_compression"") .add(""--normalize"") .addExecPath(""--sources"", compressedApk) .addExecPath(""--output"", outApk); setSingleJarCreatedBy(ruleContext, singleJarCommandLine); setSingleJarAsExecutable(ruleContext, singleJarActionBuilder); if (javaResourceZip != null) { // The javaResourceZip contains many files that are unwanted in the APK such as .class files. Artifact extractedJavaResourceZip = getApkArtifact(ruleContext, ""extracted_"" + javaResourceZip.getFilename()); ruleContext.registerAction( createSpawnActionBuilder(ruleContext) .setExecutable(resourceExtractor) .setMnemonic(""ResourceExtractor"") .setProgressMessage(""Extracting Java resources from deploy jar for %s"", apkName) .addInput(javaResourceZip) .addOutput(extractedJavaResourceZip) .addCommandLine( CustomCommandLine.builder() .addExecPath(javaResourceZip) .addExecPath(extractedJavaResourceZip) .build()) .useDefaultShellEnvironment() .build(ruleContext)); if (ruleContext.getFragment(AndroidConfiguration.class).compressJavaResources()) { compressedApkActionBuilder.addInput(extractedJavaResourceZip); compressedApkCommandLine.addExecPath(""--sources"", extractedJavaResourceZip); } else { singleJarActionBuilder.addInput(extractedJavaResourceZip); singleJarCommandLine.addExecPath(""--sources"", extractedJavaResourceZip); } } if (nativeLibs.getName() != null) { singleJarActionBuilder.addInput(nativeLibs.getName()); singleJarCommandLine .add(""--resources"") .addFormatted(""%s:%s"", nativeLibs.getName(), nativeLibs.getName().getFilename()); } for (Artifact inputZip : inputZips.build()) { singleJarActionBuilder.addInput(inputZip); singleJarCommandLine.addExecPath(""--sources"", inputZip); } List noCompressExtensions; if (ruleContext .getRule() .isAttrDefined(AndroidRuleClasses.NOCOMPRESS_EXTENSIONS_ATTR, Type.STRING_LIST)) { noCompressExtensions = ruleContext .getExpander() .withDataLocations() .tokenized(AndroidRuleClasses.NOCOMPRESS_EXTENSIONS_ATTR); } else { // This code is also used by android_test, which doesn't have this attribute. noCompressExtensions = ImmutableList.of(); } if (!noCompressExtensions.isEmpty()) { compressedApkCommandLine.addAll(""--nocompress_suffixes"", noCompressExtensions); singleJarCommandLine.addAll(""--nocompress_suffixes"", noCompressExtensions); } compressedApkActionBuilder.addCommandLine(compressedApkCommandLine.build()); ruleContext.registerAction(compressedApkActionBuilder.build(ruleContext)); singleJarActionBuilder.addCommandLine(singleJarCommandLine.build()); ruleContext.registerAction(singleJarActionBuilder.build(ruleContext)); } /** Uses the zipalign tool to align the zip boundaries for uncompressed resources by 4 bytes. */ private void zipalignApk(RuleContext ruleContext, Artifact inputApk, Artifact [MASK] ) { ruleContext.registerAction( createSpawnActionBuilder(ruleContext) .addInput(inputApk) .addOutput( [MASK] ) .setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getZipalign()) .setProgressMessage(""Zipaligning %s"", apkName) .setMnemonic(""AndroidZipAlign"") .addInput(inputApk) .addOutput( [MASK] ) .addCommandLine( CustomCommandLine.builder() .add(""-p"") // memory page aligment for stored shared object files .add(""4"") .addExecPath(inputApk) .addExecPath( [MASK] ) .build()) .build(ruleContext)); } /** * Signs an APK using the ApkSignerTool. Supports both the jar signing scheme(v1) and the apk * signing scheme v2. Note that zip alignment is preserved by this step. Furthermore, zip * alignment cannot be performed after v2 signing without invalidating the signature. */ private void signApk( RuleContext ruleContext, Artifact unsignedApk, Artifact signedAndZipalignedApk) { ApkSigningMethod signingMethod = ruleContext.getFragment(AndroidConfiguration.class).getApkSigningMethod(); SpawnAction.Builder actionBuilder = createSpawnActionBuilder(ruleContext) .setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getApkSigner()) .setProgressMessage(""Signing %s"", apkName) .setMnemonic(""ApkSignerTool"") .addOutput(signedAndZipalignedApk) .addInput(unsignedApk); CustomCommandLine.Builder commandLine = CustomCommandLine.builder().add(""sign""); actionBuilder.addInputs(signingKeys); if (signingLineage != null) { actionBuilder.addInput(signingLineage); commandLine.add(""--lineage"").addExecPath(signingLineage); } if (deterministicSigning) { // Enable deterministic DSA signing to keep the output of apksigner deterministic. // This requires including BouncyCastleProvider as a Security provider, since the standard // JDK Security providers do not include support for deterministic DSA signing. // Since this adds BouncyCastleProvider to the end of the Provider list, any non-DSA signing // algorithms (such as RSA) invoked by apksigner will still use the standard JDK // implementations and not Bouncy Castle. commandLine.add(""--deterministic-dsa-signing"", ""true""); commandLine.add(""--provider-class"", ""org.bouncycastle.jce.provider.BouncyCastleProvider""); } for (int i = 0; i < signingKeys.size(); i++) { if (i > 0) { commandLine.add(""--next-signer""); } commandLine.add(""--ks"").addExecPath(signingKeys.get(i)).add(""--ks-pass"", ""pass:android""); } commandLine .add(""--v1-signing-enabled"", Boolean.toString(signingMethod.signV1())) .add(""--v1-signer-name"", ""CERT"") .add(""--v2-signing-enabled"", Boolean.toString(signingMethod.signV2())); if (signingMethod.signV4() != null) { commandLine.add(""--v4-signing-enabled"", Boolean.toString(signingMethod.signV4())); } if (!Strings.isNullOrEmpty(signingKeyRotationMinSdk)) { commandLine.add(""--rotation-min-sdk-version"", signingKeyRotationMinSdk); } commandLine.add(""--out"").addExecPath(signedAndZipalignedApk).addExecPath(unsignedApk); if (v4SignatureFile != null) { actionBuilder.addOutput(v4SignatureFile); } ruleContext.registerAction( actionBuilder.addCommandLine(commandLine.build()).build(ruleContext)); } private static void setSingleJarAsExecutable( RuleContext ruleContext, SpawnAction.Builder builder) { FilesToRunProvider singleJar = JavaToolchainProvider.from(ruleContext).getSingleJar(); builder.setExecutable(singleJar); } private Artifact getApkArtifact(RuleContext ruleContext, String baseName) { if (artifactLocation != null) { return ruleContext.getUniqueDirectoryArtifact( artifactLocation, baseName, ruleContext.getBinOrGenfilesDirectory()); } else { return AndroidBinary.getDxArtifact(ruleContext, baseName); } } /** Adds execution info by propagating tags from the target */ private static SpawnAction.Builder createSpawnActionBuilder(RuleContext ruleContext) { return new SpawnAction.Builder() .setExecutionInfo( TargetUtils.getExecutionInfo( ruleContext.getRule(), ruleContext.isAllowTagsPropagation())); } } ","zipAlignedApk " "// Copyright 2018 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.blackbox.framework; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.devtools.build.lib.blackbox.framework.ProcessRunner.ProcessRunnerException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Class for running Bazel process in the working directory of the blackbox test. * *

    Provides customization methods for timeout, environment variables, etc., which modify the * current instance of BuilderRunner, and also return it for chain calls. * *

    The instance keeps only parameters for the Bazel invocations, like timeout or environment * variables values, but not the data related to the actual Bazel invocations. That is why the same * instance can be used to invoke several commands. */ public final class BuilderRunner { private static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30); private final Path workDir; private final Path binaryPath; private final Map env; private final ExecutorService executorService; private long timeoutMillis; private boolean useDefaultRc = true; private int errorCode = 0; private List flags; private boolean shouldFail; private boolean enableDebug; /** * Creates the BuilderRunner * * @param workDir working directory of the test * @param binaryPath {@link Path} to the Bazel executable * @param defaultTimeoutMillis default timeout in milliseconds to use if the user has not * specified timeout for Bazel command invocation * @param env environment variables to be passed to Bazel process (part, common for all Bazel * invocations) * @param executorService {@link ExecutorService} to be used by the {@link ProcessRunner}, which * actually invokes the Bzel command */ BuilderRunner( Path workDir, Path binaryPath, long defaultTimeoutMillis, Map env, ExecutorService executorService) { Preconditions.checkNotNull(workDir); Preconditions.checkNotNull(binaryPath); Preconditions.checkNotNull(env); Preconditions.checkState(defaultTimeoutMillis > 0, ""Expected default timeout to be positive""); this.workDir = workDir; this.binaryPath = binaryPath; this.env = Maps.newHashMap(env); this.executorService = executorService; this.timeoutMillis = defaultTimeoutMillis; this.flags = Lists.newArrayList(); } /** * Sets environment variable for the Bazel invocation. * * @param name name of the variable * @param value value of variable * @return this BuildRunner instance */ public BuilderRunner withEnv(String name, String value) { env.put(name, value); return this; } /** * Sets the expected error code. * * @param errorCode the value of the error code * @return this BuildRunner instance */ public BuilderRunner withErrorCode(int errorCode) { this.errorCode = errorCode; return this; } /** * Expect Bazel to fail. This method is needed when the exact error code can not be specified. * * @return this BuildRunner instance */ public BuilderRunner shouldFail() { this.shouldFail = true; return this; } /** * Sets timeout value for the Bazel process invocation. If not called, default value is used, * which is calculated from the test parameters. See {@link * BlackBoxTestContext#getTestTimeoutMillis()}. If the invocation time exceeds timeout, {@link * TimeoutException} is thrown. * * @param timeoutMillis timeout value in milliseconds * @return this BuilderRunner instance */ public BuilderRunner withTimeout(long timeoutMillis) { Preconditions.checkState(this.timeoutMillis > 0); this.timeoutMillis = timeoutMillis; return this; } /** * Should be used ONLY FOR TESTS DEBUG. Adds ""--host_jvm_debug"" to the Bazel startup options, so * that the JVM waits for the debugger process to connect before executing any code. * * @return this BuilderRunner instance */ public BuilderRunner enableDebug() { this.enableDebug = true; return this; } /** * Specifies that Bazel should not pass the default .bazelrc (in the test working directory) as * parameter * * @return this BuilderRunner instance */ public BuilderRunner withoutDefaultRc() { useDefaultRc = false; return this; } /** * Specifies the flags to pass to Bazel. * *

    We need it as a builder method, so that several consequent Bazel calls with the same set of * flags could be performed: bazel build --flag1 --flag2 //... bazel info --flag1 --flag2 * bazel-bin * * @return this BuilderRunner instance */ public BuilderRunner withFlags(String... flags) { Collections.addAll(this.flags, flags); return this; } /** * Runs bazel info <parameter> and returns the result. Asserts the process exit * code. Does not assert that the error stream is empty. * * @param parameters - info command parameter (can be omitted) and the Bazel flags. If Bazel was * invoked with some flags, the same set of flags should be used with info. * @return ProcessResult with process exit code, strings with stdout and error streams contents * @throws TimeoutException in case of timeout * @throws IOException in case of the process startup/interaction problems * @throws InterruptedException if the current thread is interrupted while waiting * @throws ProcessRunnerException if the process return code is not zero or error stream is not * empty when it was expected */ public ProcessResult info(String... parameters) throws Exception { // additional expectations for the info time to be under the default timeout withTimeout(Math.min(DEFAULT_TIMEOUT_MILLIS, timeoutMillis)); return runBinary(""info"", parameters); } /** * Runs bazel help and returns the result. Asserts the process exit code. Does not * assert that the error stream is empty. * * @return ProcessResult with process exit code, strings with stdout and error streams contents * @throws TimeoutException in case of timeout * @throws IOException in case of the process startup/interaction problems * @throws InterruptedException if the current thread is interrupted while waiting * @throws ProcessRunnerException if the process return code is not zero or error stream is not * empty when it was expected */ public ProcessResult help() throws Exception { // additional expectations for the info time to be under the default timeout withTimeout(Math.min(DEFAULT_TIMEOUT_MILLIS, timeoutMillis)); return runBinary(""help""); } /** * Runs bazel test <args> and returns the result. Asserts that the process exit * code is zero. Does not assert that the error stream is empty. * * @param args arguments to pass to test command * @return ProcessResult with process exit code, strings with stdout and error streams contents * @throws TimeoutException in case of timeout * @throws IOException in case of the process startup/interaction problems * @throws InterruptedException if the current thread is interrupted while waiting * @throws ProcessRunnerException if the process return code is not zero or error stream is not * empty when it was expected */ public ProcessResult test(String... args) throws Exception { return runBinary(""test"", args); } /** * Runs bazel build <args> and returns the result. Asserts that the process * exit code is zero. Does not assert that the error stream is empty. * * @param args arguments to pass to build command * @return ProcessResult with process exit code, strings with stdout and error streams contents * @throws TimeoutException in case of timeout * @throws IOException in case of the process startup/interaction problems * @throws InterruptedException if the current thread is interrupted while waiting * @throws ProcessRunnerException if the process return code is not zero or error stream is not * empty when it was expected */ public ProcessResult build(String... args) throws Exception { return runBinary(""build"", args); } /** * Runs bazel query <args> and returns the result. Asserts that the process * exit code is zero. Does not assert that the error stream is empty. * * @param args arguments to pass to query command * @return ProcessResult with process exit code, strings with stdout and error streams contents * @throws TimeoutException in case of timeout * @throws IOException in case of the process startup/interaction problems * @throws InterruptedException if the current thread is interrupted while waiting * @throws ProcessRunnerException if the process return code is not zero or error stream is not * empty when it was expected */ public ProcessResult query(String... args) throws Exception { return runBinary(""query"", args); } /** * Runs bazel run <args> and returns the result. Asserts that the process exit * code is zero. Does not assert that the error stream is empty. * * @param args arguments to pass to run command * @return ProcessResult with process exit code, strings with stdout and error streams contents * @throws TimeoutException in case of timeout * @throws IOException in case of the process startup/interaction problems * @throws InterruptedException if the current thread is interrupted while waiting * @throws ProcessRunnerException if the process return code is not zero or error stream is not * empty when it was expected */ public ProcessResult run(String... args) throws Exception { return runBinary(""run"", args); } /** * Runs bazel shutdown and returns the result. Asserts that the process exit code is * zero. Does not assert that the error stream is empty. * * @return ProcessResult with process exit code, strings with stdout and error streams contents * @throws TimeoutException in case of timeout * @throws IOException in case of the process startup/interaction problems * @throws InterruptedException if the current thread is interrupted while waiting * @throws ProcessRunnerException if the process return code is not zero or error stream is not * empty when it was expected */ public ProcessResult shutdown() throws Exception { // additional expectations for the shutdown time to be under the default timeout withTimeout(Math.min(DEFAULT_TIMEOUT_MILLIS, timeoutMillis)); return runBinary(""shutdown""); } private ProcessResult runBinary(String command, String... args) throws Exception { List list = Lists.newArrayList(); if (useDefaultRc) { Path [MASK] = workDir.resolve("".bazelrc""); if (Files.exists( [MASK] )) { list.add(""--bazelrc""); list.add( [MASK] .toAbsolutePath().toString()); } } if (enableDebug) { list.add(""--host_jvm_debug""); // 10 min for debug list.add(""--max_idle_secs=600""); } list.add(command); list.addAll(this.flags); Collections.addAll(list, args); ProcessParameters parameters = ProcessParameters.builder() .setWorkingDirectory(workDir.toFile()) .setName(binaryPath.toString()) .setTimeoutMillis(timeoutMillis) .setArguments(list) .setEnvironment(ImmutableMap.copyOf(env)) // bazel writes info messages to error stream, so // we need to allow the error output stream be not empty .setExpectedEmptyError(false) .setExpectedExitCode(errorCode) .setExpectedToFail(shouldFail) .build(); return new ProcessRunner(parameters, executorService).runSynchronously(); } } ","bazelRc " "package com.github.scribejava.core.oauth2.bearersignature; import com.github.scribejava.core.model.OAuthConstants; import com.github.scribejava.core.model.OAuthRequest; /** * 2.3. URI Query Parameter
    * https://tools.ietf.org/html/rfc6750#section-2.3 */ public class BearerSignatureURIQueryParameter implements BearerSignature { protected BearerSignatureURIQueryParameter() { } private static class InstanceHolder { private static final BearerSignatureURIQueryParameter INSTANCE = new BearerSignatureURIQueryParameter(); } public static BearerSignatureURIQueryParameter instance() { return InstanceHolder.INSTANCE; } @Override public void signRequest(String accessToken, OAuthRequest [MASK] ) { [MASK] .addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, accessToken); } } ","request " "/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 2007-2013, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package android.icu.impl.duration; import java.text.FieldPosition; import java.util.Date; import android.icu.text.DurationFormat; import android.icu.util.ULocale; /** * @author srl * @hide Only a subset of ICU is exposed in Android */ public class BasicDurationFormat extends DurationFormat { /** * */ private static final long serialVersionUID = -3146984141909457700L; transient DurationFormatter formatter; transient PeriodFormatter pformatter; transient PeriodFormatterService pfs = null; public static BasicDurationFormat getInstance(ULocale locale) { return new BasicDurationFormat(locale); } public StringBuffer format(Object object, StringBuffer toAppend, FieldPosition pos) { if(object instanceof Long) { String res = formatDurationFromNow(((Long)object).longValue()); return toAppend.append(res); } else if(object instanceof Date) { String res = formatDurationFromNowTo(((Date)object)); return toAppend.append(res); } else if (object instanceof javax.xml.datatype.Duration) { String res = formatDuration(object); return toAppend.append(res); } throw new IllegalArgumentException(""Cannot format given Object as a Duration""); } public BasicDurationFormat() { pfs = BasicPeriodFormatterService.getInstance(); formatter = pfs.newDurationFormatterFactory().getFormatter(); pformatter = pfs.newPeriodFormatterFactory().setDisplayPastFuture(false).getFormatter(); } /** * */ public BasicDurationFormat(ULocale locale) { super(locale); pfs = BasicPeriodFormatterService.getInstance(); formatter = pfs.newDurationFormatterFactory().setLocale(locale.getName()).getFormatter(); pformatter = pfs.newPeriodFormatterFactory().setDisplayPastFuture(false).setLocale(locale.getName()).getFormatter(); } /* (non-Javadoc) * @see android.icu.text.DurationFormat#formatDurationFrom(long, long) */ public String formatDurationFrom(long duration, long referenceDate) { return formatter.formatDurationFrom(duration, referenceDate); } /* (non-Javadoc) * @see android.icu.text.DurationFormat#formatDurationFromNow(long) */ public String formatDurationFromNow(long duration) { return formatter.formatDurationFromNow(duration); } /* (non-Javadoc) * @see android.icu.text.DurationFormat#formatDurationFromNowTo(java.util.Date) */ public String formatDurationFromNowTo(Date targetDate) { return formatter.formatDurationFromNowTo(targetDate); } /** * JDK 1.5+ only * @param obj Object being passed. * @return The PeriodFormatter object formatted to the object passed. * @see ""http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/datatype/Duration.html"" */ public String formatDuration(Object obj) { javax.xml.datatype.DatatypeConstants.Field inFields[] = { javax.xml.datatype.DatatypeConstants.YEARS, javax.xml.datatype.DatatypeConstants.MONTHS, javax.xml.datatype.DatatypeConstants.DAYS, javax.xml.datatype.DatatypeConstants.HOURS, javax.xml.datatype.DatatypeConstants.MINUTES, javax.xml.datatype.DatatypeConstants.SECONDS, }; TimeUnit outFields[] = { TimeUnit.YEAR, TimeUnit.MONTH, TimeUnit.DAY, TimeUnit.HOUR, TimeUnit.MINUTE, TimeUnit.SECOND, }; javax.xml.datatype.Duration inDuration = (javax.xml.datatype.Duration)obj; Period p = null; javax.xml.datatype.Duration duration = inDuration; boolean [MASK] = false; if(inDuration.getSign()<0) { duration = inDuration.negate(); [MASK] = true; } // convert a Duration to a Period boolean sawNonZero = false; // did we have a set, non-zero field? for(int i=0;i 0.0) { alternateUnit = TimeUnit.MILLISECOND; alternateVal=(float)millis; floatVal=(float)intSeconds; } } if(p == null) { p = Period.at(floatVal, outFields[i]); } else { p = p.and(floatVal, outFields[i]); } if(alternateUnit != null) { p = p.and(alternateVal, alternateUnit); // add in MILLISECONDs } } } if(p == null) { // no fields set = 0 seconds return formatDurationFromNow(0); } else { if( [MASK] ) {// was negated, above. p = p. [MASK] (); } else { p = p.inFuture(); } } // now, format it. return pformatter.format(p); } } ","inPast " "/* ---------------------------------------------------------------------------- * This file was automatically generated by SWIG (http://www.swig.org). * Version 3.0.11 * * Do not make changes to this file unless you know what you are doing--modify * the SWIG interface file instead. * ----------------------------------------------------------------------------- */ package com.badlogic.gdx.physics.bullet.dynamics; import com.badlogic.gdx.physics.bullet.linearmath.*; import com.badlogic.gdx.physics.bullet.collision.*; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Matrix4; public class btHingeConstraint extends btTypedConstraint { private long swigCPtr; protected btHingeConstraint (final String className, long cPtr, boolean cMemoryOwn) { super(className, DynamicsJNI.btHingeConstraint_SWIGUpcast(cPtr), cMemoryOwn); swigCPtr = cPtr; } /** Construct a new btHingeConstraint, normally you should not need this constructor it's intended for low-level usage. */ public btHingeConstraint (long cPtr, boolean cMemoryOwn) { this(""btHingeConstraint"", cPtr, cMemoryOwn); construct(); } @Override protected void reset (long cPtr, boolean cMemoryOwn) { if (!destroyed) destroy(); super.reset(DynamicsJNI.btHingeConstraint_SWIGUpcast(swigCPtr = cPtr), cMemoryOwn); } public static long getCPtr (btHingeConstraint obj) { return (obj == null) ? 0 : obj.swigCPtr; } @Override protected void finalize () throws Throwable { if (!destroyed) destroy(); super.finalize(); } @Override protected synchronized void delete () { if (swigCPtr != 0) { if (swigCMemOwn) { swigCMemOwn = false; DynamicsJNI.delete_btHingeConstraint(swigCPtr); } swigCPtr = 0; } super.delete(); } public long operatorNew (long [MASK] ) { return DynamicsJNI.btHingeConstraint_operatorNew__SWIG_0(swigCPtr, this, [MASK] ); } public void operatorDelete (long ptr) { DynamicsJNI.btHingeConstraint_operatorDelete__SWIG_0(swigCPtr, this, ptr); } public long operatorNew (long arg0, long ptr) { return DynamicsJNI.btHingeConstraint_operatorNew__SWIG_1(swigCPtr, this, arg0, ptr); } public void operatorDelete (long arg0, long arg1) { DynamicsJNI.btHingeConstraint_operatorDelete__SWIG_1(swigCPtr, this, arg0, arg1); } public long operatorNewArray (long [MASK] ) { return DynamicsJNI.btHingeConstraint_operatorNewArray__SWIG_0(swigCPtr, this, [MASK] ); } public void operatorDeleteArray (long ptr) { DynamicsJNI.btHingeConstraint_operatorDeleteArray__SWIG_0(swigCPtr, this, ptr); } public long operatorNewArray (long arg0, long ptr) { return DynamicsJNI.btHingeConstraint_operatorNewArray__SWIG_1(swigCPtr, this, arg0, ptr); } public void operatorDeleteArray (long arg0, long arg1) { DynamicsJNI.btHingeConstraint_operatorDeleteArray__SWIG_1(swigCPtr, this, arg0, arg1); } public btHingeConstraint (btRigidBody rbA, btRigidBody rbB, Vector3 pivotInA, Vector3 pivotInB, Vector3 axisInA, Vector3 axisInB, boolean useReferenceFrameA) { this(DynamicsJNI.new_btHingeConstraint__SWIG_0(btRigidBody.getCPtr(rbA), rbA, btRigidBody.getCPtr(rbB), rbB, pivotInA, pivotInB, axisInA, axisInB, useReferenceFrameA), true); } public btHingeConstraint (btRigidBody rbA, btRigidBody rbB, Vector3 pivotInA, Vector3 pivotInB, Vector3 axisInA, Vector3 axisInB) { this(DynamicsJNI.new_btHingeConstraint__SWIG_1(btRigidBody.getCPtr(rbA), rbA, btRigidBody.getCPtr(rbB), rbB, pivotInA, pivotInB, axisInA, axisInB), true); } public btHingeConstraint (btRigidBody rbA, Vector3 pivotInA, Vector3 axisInA, boolean useReferenceFrameA) { this(DynamicsJNI.new_btHingeConstraint__SWIG_2(btRigidBody.getCPtr(rbA), rbA, pivotInA, axisInA, useReferenceFrameA), true); } public btHingeConstraint (btRigidBody rbA, Vector3 pivotInA, Vector3 axisInA) { this(DynamicsJNI.new_btHingeConstraint__SWIG_3(btRigidBody.getCPtr(rbA), rbA, pivotInA, axisInA), true); } public btHingeConstraint (btRigidBody rbA, btRigidBody rbB, Matrix4 rbAFrame, Matrix4 rbBFrame, boolean useReferenceFrameA) { this(DynamicsJNI.new_btHingeConstraint__SWIG_4(btRigidBody.getCPtr(rbA), rbA, btRigidBody.getCPtr(rbB), rbB, rbAFrame, rbBFrame, useReferenceFrameA), true); } public btHingeConstraint (btRigidBody rbA, btRigidBody rbB, Matrix4 rbAFrame, Matrix4 rbBFrame) { this(DynamicsJNI.new_btHingeConstraint__SWIG_5(btRigidBody.getCPtr(rbA), rbA, btRigidBody.getCPtr(rbB), rbB, rbAFrame, rbBFrame), true); } public btHingeConstraint (btRigidBody rbA, Matrix4 rbAFrame, boolean useReferenceFrameA) { this(DynamicsJNI.new_btHingeConstraint__SWIG_6(btRigidBody.getCPtr(rbA), rbA, rbAFrame, useReferenceFrameA), true); } public btHingeConstraint (btRigidBody rbA, Matrix4 rbAFrame) { this(DynamicsJNI.new_btHingeConstraint__SWIG_7(btRigidBody.getCPtr(rbA), rbA, rbAFrame), true); } public void getInfo1NonVirtual (btTypedConstraint.btConstraintInfo1 info) { DynamicsJNI.btHingeConstraint_getInfo1NonVirtual(swigCPtr, this, btTypedConstraint.btConstraintInfo1.getCPtr(info), info); } public void getInfo2NonVirtual (btTypedConstraint.btConstraintInfo2 info, Matrix4 transA, Matrix4 transB, Vector3 angVelA, Vector3 angVelB) { DynamicsJNI.btHingeConstraint_getInfo2NonVirtual(swigCPtr, this, btTypedConstraint.btConstraintInfo2.getCPtr(info), info, transA, transB, angVelA, angVelB); } public void getInfo2Internal (btTypedConstraint.btConstraintInfo2 info, Matrix4 transA, Matrix4 transB, Vector3 angVelA, Vector3 angVelB) { DynamicsJNI.btHingeConstraint_getInfo2Internal(swigCPtr, this, btTypedConstraint.btConstraintInfo2.getCPtr(info), info, transA, transB, angVelA, angVelB); } public void getInfo2InternalUsingFrameOffset (btTypedConstraint.btConstraintInfo2 info, Matrix4 transA, Matrix4 transB, Vector3 angVelA, Vector3 angVelB) { DynamicsJNI.btHingeConstraint_getInfo2InternalUsingFrameOffset(swigCPtr, this, btTypedConstraint.btConstraintInfo2.getCPtr(info), info, transA, transB, angVelA, angVelB); } public void updateRHS (float timeStep) { DynamicsJNI.btHingeConstraint_updateRHS(swigCPtr, this, timeStep); } public btRigidBody getRigidBodyAConst () { return btRigidBody.getInstance(DynamicsJNI.btHingeConstraint_getRigidBodyAConst(swigCPtr, this), false); } public btRigidBody getRigidBodyBConst () { return btRigidBody.getInstance(DynamicsJNI.btHingeConstraint_getRigidBodyBConst(swigCPtr, this), false); } public btRigidBody getRigidBodyA () { return btRigidBody.getInstance(DynamicsJNI.btHingeConstraint_getRigidBodyA(swigCPtr, this), false); } public btRigidBody getRigidBodyB () { return btRigidBody.getInstance(DynamicsJNI.btHingeConstraint_getRigidBodyB(swigCPtr, this), false); } public Matrix4 getFrameOffsetA () { return DynamicsJNI.btHingeConstraint_getFrameOffsetA(swigCPtr, this); } public Matrix4 getFrameOffsetB () { return DynamicsJNI.btHingeConstraint_getFrameOffsetB(swigCPtr, this); } public void setFrames (Matrix4 frameA, Matrix4 frameB) { DynamicsJNI.btHingeConstraint_setFrames(swigCPtr, this, frameA, frameB); } public void setAngularOnly (boolean angularOnly) { DynamicsJNI.btHingeConstraint_setAngularOnly(swigCPtr, this, angularOnly); } public void enableAngularMotor (boolean enableMotor, float targetVelocity, float maxMotorImpulse) { DynamicsJNI.btHingeConstraint_enableAngularMotor(swigCPtr, this, enableMotor, targetVelocity, maxMotorImpulse); } public void enableMotor (boolean enableMotor) { DynamicsJNI.btHingeConstraint_enableMotor(swigCPtr, this, enableMotor); } public void setMaxMotorImpulse (float maxMotorImpulse) { DynamicsJNI.btHingeConstraint_setMaxMotorImpulse(swigCPtr, this, maxMotorImpulse); } public void setMotorTargetVelocity (float motorTargetVelocity) { DynamicsJNI.btHingeConstraint_setMotorTargetVelocity(swigCPtr, this, motorTargetVelocity); } public void setMotorTarget (Quaternion qAinB, float dt) { DynamicsJNI.btHingeConstraint_setMotorTarget__SWIG_0(swigCPtr, this, qAinB, dt); } public void setMotorTarget (float targetAngle, float dt) { DynamicsJNI.btHingeConstraint_setMotorTarget__SWIG_1(swigCPtr, this, targetAngle, dt); } public void setLimit (float low, float high, float _softness, float _biasFactor, float _relaxationFactor) { DynamicsJNI.btHingeConstraint_setLimit__SWIG_0(swigCPtr, this, low, high, _softness, _biasFactor, _relaxationFactor); } public void setLimit (float low, float high, float _softness, float _biasFactor) { DynamicsJNI.btHingeConstraint_setLimit__SWIG_1(swigCPtr, this, low, high, _softness, _biasFactor); } public void setLimit (float low, float high, float _softness) { DynamicsJNI.btHingeConstraint_setLimit__SWIG_2(swigCPtr, this, low, high, _softness); } public void setLimit (float low, float high) { DynamicsJNI.btHingeConstraint_setLimit__SWIG_3(swigCPtr, this, low, high); } public float getLimitSoftness () { return DynamicsJNI.btHingeConstraint_getLimitSoftness(swigCPtr, this); } public float getLimitBiasFactor () { return DynamicsJNI.btHingeConstraint_getLimitBiasFactor(swigCPtr, this); } public float getLimitRelaxationFactor () { return DynamicsJNI.btHingeConstraint_getLimitRelaxationFactor(swigCPtr, this); } public void setAxis (Vector3 axisInA) { DynamicsJNI.btHingeConstraint_setAxis(swigCPtr, this, axisInA); } public boolean hasLimit () { return DynamicsJNI.btHingeConstraint_hasLimit(swigCPtr, this); } public float getLowerLimit () { return DynamicsJNI.btHingeConstraint_getLowerLimit(swigCPtr, this); } public float getUpperLimit () { return DynamicsJNI.btHingeConstraint_getUpperLimit(swigCPtr, this); } public float getHingeAngle () { return DynamicsJNI.btHingeConstraint_getHingeAngle__SWIG_0(swigCPtr, this); } public float getHingeAngle (Matrix4 transA, Matrix4 transB) { return DynamicsJNI.btHingeConstraint_getHingeAngle__SWIG_1(swigCPtr, this, transA, transB); } public void testLimit (Matrix4 transA, Matrix4 transB) { DynamicsJNI.btHingeConstraint_testLimit(swigCPtr, this, transA, transB); } public Matrix4 getAFrameConst () { return DynamicsJNI.btHingeConstraint_getAFrameConst(swigCPtr, this); } public Matrix4 getBFrameConst () { return DynamicsJNI.btHingeConstraint_getBFrameConst(swigCPtr, this); } public Matrix4 getAFrame () { return DynamicsJNI.btHingeConstraint_getAFrame(swigCPtr, this); } public Matrix4 getBFrame () { return DynamicsJNI.btHingeConstraint_getBFrame(swigCPtr, this); } public int getSolveLimit () { return DynamicsJNI.btHingeConstraint_getSolveLimit(swigCPtr, this); } public float getLimitSign () { return DynamicsJNI.btHingeConstraint_getLimitSign(swigCPtr, this); } public boolean getAngularOnly () { return DynamicsJNI.btHingeConstraint_getAngularOnly(swigCPtr, this); } public boolean getEnableAngularMotor () { return DynamicsJNI.btHingeConstraint_getEnableAngularMotor(swigCPtr, this); } public float getMotorTargetVelocity () { return DynamicsJNI.btHingeConstraint_getMotorTargetVelocity(swigCPtr, this); } public float getMaxMotorImpulse () { return DynamicsJNI.btHingeConstraint_getMaxMotorImpulse(swigCPtr, this); } public boolean getUseFrameOffset () { return DynamicsJNI.btHingeConstraint_getUseFrameOffset(swigCPtr, this); } public void setUseFrameOffset (boolean frameOffsetOnOff) { DynamicsJNI.btHingeConstraint_setUseFrameOffset(swigCPtr, this, frameOffsetOnOff); } public boolean getUseReferenceFrameA () { return DynamicsJNI.btHingeConstraint_getUseReferenceFrameA(swigCPtr, this); } public void setUseReferenceFrameA (boolean useReferenceFrameA) { DynamicsJNI.btHingeConstraint_setUseReferenceFrameA(swigCPtr, this, useReferenceFrameA); } public void setParam (int num, float value, int axis) { DynamicsJNI.btHingeConstraint_setParam__SWIG_0(swigCPtr, this, num, value, axis); } public void setParam (int num, float value) { DynamicsJNI.btHingeConstraint_setParam__SWIG_1(swigCPtr, this, num, value); } public float getParam (int num, int axis) { return DynamicsJNI.btHingeConstraint_getParam__SWIG_0(swigCPtr, this, num, axis); } public float getParam (int num) { return DynamicsJNI.btHingeConstraint_getParam__SWIG_1(swigCPtr, this, num); } public int getFlags () { return DynamicsJNI.btHingeConstraint_getFlags(swigCPtr, this); } } ","sizeInBytes " "package com.alibaba.json.bvt.parser; import java.io.Reader; import java.io.StringReader; import org.junit.Assert; import junit.framework.TestCase; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.parser.DefaultJSONParser; import com.alibaba.fastjson.parser.JSONReaderScanner; public class JSONReaderScannerTest_int extends TestCase { public void test_scanInt() throws Exception { StringBuffer buf = new StringBuffer(); buf.append('['); for (int i = 0; i < 1024; ++i) { if (i != 0) { buf.append(','); } buf.append(i); } buf.append(']'); Reader reader = new StringReader(buf.toString()); JSONReaderScanner [MASK] = new JSONReaderScanner(reader); DefaultJSONParser parser = new DefaultJSONParser( [MASK] ); JSONArray array = (JSONArray) parser.parse(); for (int i = 0; i < array.size(); ++i) { Assert.assertEquals(i, ((Integer) array.get(i)).intValue()); } } } ","scanner " " package com.github.mikephil.charting.data; import android.graphics.Typeface; import android.util.Log; import com.github.mikephil.charting.components.YAxis.AxisDependency; import com.github.mikephil.charting.formatter.IValueFormatter; import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.interfaces.datasets.IDataSet; import java.util.ArrayList; import java.util.List; /** * Class that holds all relevant data that represents the chart. That involves * at least one (or more) DataSets, and an array of x-values. * * @author Philipp Jahoda */ public abstract class ChartData> { /** * maximum y-value in the value array across all axes */ protected float mYMax = -Float.MAX_VALUE; /** * the minimum y-value in the value array across all axes */ protected float mYMin = Float.MAX_VALUE; /** * maximum x-value in the value array */ protected float mXMax = -Float.MAX_VALUE; /** * minimum x-value in the value array */ protected float mXMin = Float.MAX_VALUE; protected float mLeftAxisMax = -Float.MAX_VALUE; protected float mLeftAxisMin = Float.MAX_VALUE; protected float mRightAxisMax = -Float.MAX_VALUE; protected float mRightAxisMin = Float.MAX_VALUE; /** * array that holds all DataSets the ChartData object represents */ protected List mDataSets; /** * Default constructor. */ public ChartData() { mDataSets = new ArrayList(); } /** * Constructor taking single or multiple DataSet objects. * * @param dataSets */ public ChartData(T... dataSets) { mDataSets = arrayToList(dataSets); notifyDataChanged(); } /** * Created because Arrays.asList(...) does not support modification. * * @param array * @return */ private List arrayToList(T[] array) { List list = new ArrayList<>(); for (T set : array) { list.add(set); } return list; } /** * constructor for chart data * * @param sets the dataset array */ public ChartData(List sets) { this.mDataSets = sets; notifyDataChanged(); } /** * Call this method to let the ChartData know that the underlying data has * changed. Calling this performs all necessary recalculations needed when * the contained data has changed. */ public void notifyDataChanged() { calcMinMax(); } /** * Calc minimum and maximum y-values over all DataSets. * Tell DataSets to recalculate their min and max y-values, this is only needed for autoScaleMinMax. * * @param fromX the x-value to start the calculation from * @param toX the x-value to which the calculation should be performed */ public void calcMinMaxY(float fromX, float toX) { for (T set : mDataSets) { set.calcMinMaxY(fromX, toX); } // apply the new data calcMinMax(); } /** * Calc minimum and maximum values (both x and y) over all DataSets. */ protected void calcMinMax() { if (mDataSets == null) return; mYMax = -Float.MAX_VALUE; mYMin = Float.MAX_VALUE; mXMax = -Float.MAX_VALUE; mXMin = Float.MAX_VALUE; for (T set : mDataSets) { calcMinMax(set); } mLeftAxisMax = -Float.MAX_VALUE; mLeftAxisMin = Float.MAX_VALUE; mRightAxisMax = -Float.MAX_VALUE; mRightAxisMin = Float.MAX_VALUE; // left axis T firstLeft = getFirstLeft(mDataSets); if (firstLeft != null) { mLeftAxisMax = firstLeft.getYMax(); mLeftAxisMin = firstLeft.getYMin(); for (T dataSet : mDataSets) { if (dataSet.getAxisDependency() == AxisDependency.LEFT) { if (dataSet.getYMin() < mLeftAxisMin) mLeftAxisMin = dataSet.getYMin(); if (dataSet.getYMax() > mLeftAxisMax) mLeftAxisMax = dataSet.getYMax(); } } } // right axis T firstRight = getFirstRight(mDataSets); if (firstRight != null) { mRightAxisMax = firstRight.getYMax(); mRightAxisMin = firstRight.getYMin(); for (T dataSet : mDataSets) { if (dataSet.getAxisDependency() == AxisDependency.RIGHT) { if (dataSet.getYMin() < mRightAxisMin) mRightAxisMin = dataSet.getYMin(); if (dataSet.getYMax() > mRightAxisMax) mRightAxisMax = dataSet.getYMax(); } } } } /** ONLY GETTERS AND SETTERS BELOW THIS */ /** * returns the number of LineDataSets this object contains * * @return */ public int getDataSetCount() { if (mDataSets == null) return 0; return mDataSets.size(); } /** * Returns the smallest y-value the data object contains. * * @return */ public float getYMin() { return mYMin; } /** * Returns the minimum y-value for the specified axis. * * @param axis * @return */ public float getYMin(AxisDependency axis) { if (axis == AxisDependency.LEFT) { if (mLeftAxisMin == Float.MAX_VALUE) { return mRightAxisMin; } else return mLeftAxisMin; } else { if (mRightAxisMin == Float.MAX_VALUE) { return mLeftAxisMin; } else return mRightAxisMin; } } /** * Returns the greatest y-value the data object contains. * * @return */ public float getYMax() { return mYMax; } /** * Returns the maximum y-value for the specified axis. * * @param axis * @return */ public float getYMax(AxisDependency axis) { if (axis == AxisDependency.LEFT) { if (mLeftAxisMax == -Float.MAX_VALUE) { return mRightAxisMax; } else return mLeftAxisMax; } else { if (mRightAxisMax == -Float.MAX_VALUE) { return mLeftAxisMax; } else return mRightAxisMax; } } /** * Returns the minimum x-value this data object contains. * * @return */ public float getXMin() { return mXMin; } /** * Returns the maximum x-value this data object contains. * * @return */ public float getXMax() { return mXMax; } /** * Returns all DataSet objects this ChartData object holds. * * @return */ public List getDataSets() { return mDataSets; } /** * Retrieve the index of a DataSet with a specific label from the ChartData. * Search can be case sensitive or not. IMPORTANT: This method does * calculations at runtime, do not over-use in performance critical * situations. * * @param dataSets the DataSet array to search * @param label * @param ignorecase if true, the search is not case-sensitive * @return */ protected int getDataSetIndexByLabel(List dataSets, String label, boolean ignorecase) { if (ignorecase) { for (int i = 0; i < dataSets.size(); i++) if (label.equalsIgnoreCase(dataSets.get(i).getLabel())) return i; } else { for (int i = 0; i < dataSets.size(); i++) if (label.equals(dataSets.get(i).getLabel())) return i; } return -1; } /** * Returns the labels of all DataSets as a string array. * * @return */ public String[] getDataSetLabels() { String[] types = new String[mDataSets.size()]; for (int i = 0; i < mDataSets.size(); i++) { types[i] = mDataSets.get(i).getLabel(); } return types; } /** * Get the Entry for a corresponding highlight object * * @param highlight * @return the entry that is highlighted */ public Entry getEntryForHighlight(Highlight highlight) { if (highlight.getDataSetIndex() >= mDataSets.size()) return null; else { return mDataSets.get(highlight.getDataSetIndex()).getEntryForXValue(highlight.getX(), highlight.getY()); } } /** * Returns the DataSet object with the given label. Search can be case * sensitive or not. IMPORTANT: This method does calculations at runtime. * Use with care in performance critical situations. * * @param label * @param ignorecase * @return */ public T getDataSetByLabel(String label, boolean ignorecase) { int index = getDataSetIndexByLabel(mDataSets, label, ignorecase); if (index < 0 || index >= mDataSets.size()) return null; else return mDataSets.get(index); } public T getDataSetByIndex(int index) { if (mDataSets == null || index < 0 || index >= mDataSets.size()) return null; return mDataSets.get(index); } /** * Adds a DataSet dynamically. * * @param d */ public void addDataSet(T d) { if (d == null) return; calcMinMax(d); mDataSets.add(d); } /** * Removes the given DataSet from this data object. Also recalculates all * minimum and maximum values. Returns true if a DataSet was removed, false * if no DataSet could be removed. * * @param d */ public boolean removeDataSet(T d) { if (d == null) return false; boolean removed = mDataSets.remove(d); // if a DataSet was removed if (removed) { notifyDataChanged(); } return removed; } /** * Removes the DataSet at the given index in the DataSet array from the data * object. Also recalculates all minimum and maximum values. Returns true if * a DataSet was removed, false if no DataSet could be removed. * * @param index */ public boolean removeDataSet(int index) { if (index >= mDataSets.size() || index < 0) return false; T set = mDataSets.get(index); return removeDataSet(set); } /** * Adds an Entry to the DataSet at the specified index. * Entries are added to the end of the list. * * @param e * @param dataSetIndex */ public void addEntry(Entry e, int dataSetIndex) { if (mDataSets.size() > dataSetIndex && dataSetIndex >= 0) { IDataSet set = mDataSets.get(dataSetIndex); // add the entry to the dataset if (!set.addEntry(e)) return; calcMinMax(e, set.getAxisDependency()); } else { Log.e(""addEntry"", ""Cannot add Entry because dataSetIndex too high or too low.""); } } /** * Adjusts the current minimum and maximum values based on the provided Entry object. * * @param e * @param axis */ protected void calcMinMax(Entry e, AxisDependency axis) { if (mYMax < e.getY()) mYMax = e.getY(); if (mYMin > e.getY()) mYMin = e.getY(); if (mXMax < e.getX()) mXMax = e.getX(); if (mXMin > e.getX()) mXMin = e.getX(); if (axis == AxisDependency.LEFT) { if (mLeftAxisMax < e.getY()) mLeftAxisMax = e.getY(); if (mLeftAxisMin > e.getY()) mLeftAxisMin = e.getY(); } else { if (mRightAxisMax < e.getY()) mRightAxisMax = e.getY(); if (mRightAxisMin > e.getY()) mRightAxisMin = e.getY(); } } /** * Adjusts the minimum and maximum values based on the given DataSet. * * @param d */ protected void calcMinMax(T d) { if (mYMax < d.getYMax()) mYMax = d.getYMax(); if (mYMin > d.getYMin()) mYMin = d.getYMin(); if (mXMax < d.getXMax()) mXMax = d.getXMax(); if (mXMin > d.getXMin()) mXMin = d.getXMin(); if (d.getAxisDependency() == AxisDependency.LEFT) { if (mLeftAxisMax < d.getYMax()) mLeftAxisMax = d.getYMax(); if (mLeftAxisMin > d.getYMin()) mLeftAxisMin = d.getYMin(); } else { if (mRightAxisMax < d.getYMax()) mRightAxisMax = d.getYMax(); if (mRightAxisMin > d.getYMin()) mRightAxisMin = d.getYMin(); } } /** * Removes the given Entry object from the DataSet at the specified index. * * @param e * @param dataSetIndex */ public boolean removeEntry(Entry e, int dataSetIndex) { // entry null, outofbounds if (e == null || dataSetIndex >= mDataSets.size()) return false; IDataSet set = mDataSets.get(dataSetIndex); if (set != null) { // remove the entry from the dataset boolean removed = set.removeEntry(e); if (removed) { notifyDataChanged(); } return removed; } else return false; } /** * Removes the Entry object closest to the given DataSet at the * specified index. Returns true if an Entry was removed, false if no Entry * was found that meets the specified requirements. * * @param xValue * @param dataSetIndex * @return */ public boolean removeEntry(float xValue, int dataSetIndex) { if (dataSetIndex >= mDataSets.size()) return false; IDataSet dataSet = mDataSets.get(dataSetIndex); Entry e = dataSet.getEntryForXValue(xValue, Float.NaN); if (e == null) return false; return removeEntry(e, dataSetIndex); } /** * Returns the DataSet that contains the provided Entry, or null, if no * DataSet contains this Entry. * * @param e * @return */ public T getDataSetForEntry(Entry e) { if (e == null) return null; for (int i = 0; i < mDataSets.size(); i++) { T set = mDataSets.get(i); for (int j = 0; j < set.getEntryCount(); j++) { if (e.equalTo(set.getEntryForXValue(e.getX(), e.getY()))) return set; } } return null; } /** * Returns all [MASK] s used across all DataSet objects this object * represents. * * @return */ public int[] getColors() { if (mDataSets == null) return null; int clrcnt = 0; for (int i = 0; i < mDataSets.size(); i++) { clrcnt += mDataSets.get(i).getColors().size(); } int[] [MASK] s = new int[clrcnt]; int cnt = 0; for (int i = 0; i < mDataSets.size(); i++) { List clrs = mDataSets.get(i).getColors(); for (Integer clr : clrs) { [MASK] s[cnt] = clr; cnt++; } } return [MASK] s; } /** * Returns the index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist. * * @param dataSet * @return */ public int getIndexOfDataSet(T dataSet) { return mDataSets.indexOf(dataSet); } /** * Returns the first DataSet from the datasets-array that has it's dependency on the left axis. * Returns null if no DataSet with left dependency could be found. * * @return */ protected T getFirstLeft(List sets) { for (T dataSet : sets) { if (dataSet.getAxisDependency() == AxisDependency.LEFT) return dataSet; } return null; } /** * Returns the first DataSet from the datasets-array that has it's dependency on the right axis. * Returns null if no DataSet with right dependency could be found. * * @return */ public T getFirstRight(List sets) { for (T dataSet : sets) { if (dataSet.getAxisDependency() == AxisDependency.RIGHT) return dataSet; } return null; } /** * Sets a custom IValueFormatter for all DataSets this data object contains. * * @param f */ public void setValueFormatter(IValueFormatter f) { if (f == null) return; else { for (IDataSet set : mDataSets) { set.setValueFormatter(f); } } } /** * Sets the [MASK] of the value-text ( [MASK] in which the value-labels are * drawn) for all DataSets this data object contains. * * @param [MASK] */ public void setValueTextColor(int [MASK] ) { for (IDataSet set : mDataSets) { set.setValueTextColor( [MASK] ); } } /** * Sets the same list of value- [MASK] s for all DataSets this * data object contains. * * @param [MASK] s */ public void setValueTextColors(List [MASK] s) { for (IDataSet set : mDataSets) { set.setValueTextColors( [MASK] s); } } /** * Sets the Typeface for all value-labels for all DataSets this data object * contains. * * @param tf */ public void setValueTypeface(Typeface tf) { for (IDataSet set : mDataSets) { set.setValueTypeface(tf); } } /** * Sets the size (in dp) of the value-text for all DataSets this data object * contains. * * @param size */ public void setValueTextSize(float size) { for (IDataSet set : mDataSets) { set.setValueTextSize(size); } } /** * Enables / disables drawing values (value-text) for all DataSets this data * object contains. * * @param enabled */ public void setDrawValues(boolean enabled) { for (IDataSet set : mDataSets) { set.setDrawValues(enabled); } } /** * Enables / disables highlighting values for all DataSets this data object * contains. If set to true, this means that values can * be highlighted programmatically or by touch gesture. */ public void setHighlightEnabled(boolean enabled) { for (IDataSet set : mDataSets) { set.setHighlightEnabled(enabled); } } /** * Returns true if highlighting of all underlying values is enabled, false * if not. * * @return */ public boolean isHighlightEnabled() { for (IDataSet set : mDataSets) { if (!set.isHighlightEnabled()) return false; } return true; } /** * Clears this data object from all DataSets and removes all Entries. Don't * forget to invalidate the chart after this. */ public void clearValues() { if (mDataSets != null) { mDataSets.clear(); } notifyDataChanged(); } /** * Checks if this data object contains the specified DataSet. Returns true * if so, false if not. * * @param dataSet * @return */ public boolean contains(T dataSet) { for (T set : mDataSets) { if (set.equals(dataSet)) return true; } return false; } /** * Returns the total entry count across all DataSet objects this data object contains. * * @return */ public int getEntryCount() { int count = 0; for (T set : mDataSets) { count += set.getEntryCount(); } return count; } /** * Returns the DataSet object with the maximum number of entries or null if there are no DataSets. * * @return */ public T getMaxEntryCountSet() { if (mDataSets == null || mDataSets.isEmpty()) return null; T max = mDataSets.get(0); for (T set : mDataSets) { if (set.getEntryCount() > max.getEntryCount()) max = set; } return max; } } ","color " "/* * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.devtools.j2objc.gen; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * Metadata required to create links for indexing in Kythe. * *

    The JSON objects produced by this class conform to the format expected by Kythe's * postprocessing pipeline; this allows Kythe to create links from source .java files to generated * .h files. */ public class KytheIndexingMetadata { /** * A Vname is a unique identifier for a node in a Kythe semantic graph. * *

    For more information, see the Kythe * documentation. */ static class VName { private final String corpus; private final String path; private VName(String corpus, String path) { this.corpus = corpus; this.path = path; } private String toJson() { return String.format( ""{\""corpus\"":\""%s\"",\""path\"":\""%s\"",\""language\"":\""java\""}"", corpus, path); } } static class AnchorAnchorMetadata { private final String type = ""anchor_anchor""; private final int sourceBegin; private final int sourceEnd; private final int [MASK] ; private final int targetEnd; private final String edge = ""/kythe/edge/imputes""; private final VName sourceVName; AnchorAnchorMetadata( int sourceBegin, int sourceEnd, int [MASK] , int targetEnd, String corpus, String path) { this.sourceBegin = sourceBegin; this.sourceEnd = sourceEnd; this. [MASK] = [MASK] ; this.targetEnd = targetEnd; this.sourceVName = new VName(corpus, path); } private String toJson() { return String.format( ""{\""type\"":\""%s\"",\""source_begin\"":%d,\""source_end\"":%d,\""target_begin\"":%d,"" + ""\""target_end\"":%d,\""edge\"":\""%s\"",\""source_vname\"":%s}"", type, sourceBegin, sourceEnd, [MASK] , targetEnd, edge, sourceVName.toJson()); } } private final String type = ""kythe0""; private final List meta = new ArrayList<>(); public void addAnchorAnchor( int sourceBegin, int sourceEnd, int [MASK] , int targetEnd, String sourceCorpus, String sourcePath) { meta.add( new AnchorAnchorMetadata( sourceBegin, sourceEnd, [MASK] , targetEnd, sourceCorpus, sourcePath)); } public boolean isEmpty() { return meta.isEmpty(); } public String toJson() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(String.format(""{\""type\"":\""%s\"",\""meta\"":["", type)); stringBuilder.append( meta.stream().map(AnchorAnchorMetadata::toJson).collect(Collectors.joining("",""))); stringBuilder.append(""]}""); return stringBuilder.toString(); } } ","targetBegin " "// Copyright 2021 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package com.google.devtools.build.lib.bazel.bzlmod; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule.ResolutionReason; import com.google.devtools.build.lib.cmdline.RepositoryName; /** Specifies that a module should be retrieved from an archive. */ @AutoValue public abstract class ArchiveOverride implements NonRegistryOverride { public static ArchiveOverride create( ImmutableList urls, ImmutableList patches, ImmutableList patchCmds, String integrity, String [MASK] , int patchStrip) { return new AutoValue_ArchiveOverride( urls, patches, patchCmds, integrity, [MASK] , patchStrip); } /** The URLs pointing at the archives. Can be HTTP(S) or file URLs. */ public abstract ImmutableList getUrls(); /** The patches to apply after extracting the archive. Should be a list of labels. */ public abstract ImmutableList getPatches(); /** The patch commands to execute after extracting the archive. Should be a list of commands. */ public abstract ImmutableList getPatchCmds(); /** The subresource integirty metadata of the archive. */ public abstract String getIntegrity(); /** The prefix to strip from paths in the archive. */ public abstract String getStripPrefix(); /** The number of path segments to strip from the paths in the supplied patches. */ public abstract int getPatchStrip(); /** Returns the {@link RepoSpec} that defines this repository. */ @Override public RepoSpec getRepoSpec(RepositoryName repoName) { return new ArchiveRepoSpecBuilder() .setRepoName(repoName.getName()) .setUrls(getUrls()) .setIntegrity(getIntegrity()) .setStripPrefix(getStripPrefix()) .setPatches(getPatches()) .setPatchCmds(getPatchCmds()) .setPatchStrip(getPatchStrip()) .build(); } @Override public ResolutionReason getResolutionReason() { return ResolutionReason.ARCHIVE_OVERRIDE; } } ","stripPrefix " "// Copyright 2016 The Bazel Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.testing.coverage; import static java.util.Comparator.comparing; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.jacoco.core.internal.analysis.Instruction; import org.jacoco.core.internal.analysis.filter.IFilter; import org.jacoco.core.internal.analysis.filter.IFilterContext; import org.jacoco.core.internal.analysis.filter.IFilterOutput; import org.jacoco.core.internal.flow.IFrame; import org.jacoco.core.internal.flow.LabelInfo; import org.jacoco.core.internal.flow.MethodProbesVisitor; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; /** * The mapper is a probes visitor that will cache control flow information as well as keeping track * of the probes as the main driver generates the probe ids. Upon finishing the method it uses the * information collected to generate the mapping information between probes and the instructions. */ public class MethodProbesMapper extends MethodProbesVisitor implements IFilterOutput { /* * The implementation roughly follows the same pattern of the Analyzer class of Jacoco. * * The mapper has a few states: * * - lineMappings: a mapping between line number and labels * * - a sequence of ""instructions"", where each instruction has one or more predecessors. The * predecessor field has a sole purpose of propagating probe id. The 'merge' nodes in the CFG has * no predecessors, since the branch stops at theses points. * * - The instructions each has states that keep track of the probes that are associated with the * instruction. * * Initially the probe ids are assigned to the instructions that immediately precede the probe. At * the end of visiting the methods, the probe ids are propagated through the predecessor chains. */ // States // // These are state variables that needs to be updated in the visitor methods. // The values usually changes as we traverse the byte code. private Instruction lastInstruction = null; private int currentLine = -1; private List

    {@link #register Registered} listeners are informed at registration and whenever the network * type changes. * *

    The current network type can also be {@link #getNetworkType queried} without registration. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public final class NetworkTypeObserver { /** A listener for network type changes. */ public interface Listener { /** * Called when the network type changed or when the listener is first registered. * *

    This method is always called on the main thread. */ void onNetworkTypeChanged(@C.NetworkType int networkType); } @Nullable private static NetworkTypeObserver staticInstance; private final Handler mainHandler; // This class needs to hold weak references as it doesn't require listeners to unregister. private final CopyOnWriteArrayList> listeners; private final Object networkTypeLock; @GuardedBy(""networkTypeLock"") private @C.NetworkType int networkType; /** * Returns a network type observer instance. * * @param context A {@link Context}. */ public static synchronized NetworkTypeObserver getInstance(Context context) { if (staticInstance == null) { staticInstance = new NetworkTypeObserver(context); } return staticInstance; } /** Resets the network type observer for tests. */ @VisibleForTesting public static synchronized void resetForTests() { staticInstance = null; } private NetworkTypeObserver(Context context) { mainHandler = new Handler(Looper.getMainLooper()); listeners = new CopyOnWriteArrayList<>(); networkTypeLock = new Object(); networkType = C.NETWORK_TYPE_UNKNOWN; IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); context.registerReceiver(new Receiver(), filter); } /** * Registers a listener. * *

    The current network type will be reported to the listener after registration. * * @param listener The {@link Listener}. */ public void register(Listener listener) { removeClearedReferences(); listeners.add(new WeakReference<>(listener)); // Simulate an initial update on the main thread (like the sticky broadcast we'd receive if // we were to register a separate broadcast receiver for each listener). mainHandler.post(() -> listener.onNetworkTypeChanged(getNetworkType())); } /** Returns the current network type. */ public @C.NetworkType int getNetworkType() { synchronized (networkTypeLock) { return networkType; } } private void removeClearedReferences() { for (WeakReference listenerReference : listeners) { if (listenerReference.get() == null) { listeners.remove(listenerReference); } } } private void updateNetworkType(@C.NetworkType int networkType) { synchronized (networkTypeLock) { if (this.networkType == networkType) { return; } this.networkType = networkType; } for (WeakReference listenerReference : listeners) { @Nullable Listener listener = listenerReference.get(); if (listener != null) { listener.onNetworkTypeChanged(networkType); } else { listeners.remove(listenerReference); } } } private static @C.NetworkType int getNetworkTypeFromConnectivityManager(Context context) { NetworkInfo networkInfo; @Nullable ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivityManager == null) { return C.NETWORK_TYPE_UNKNOWN; } try { networkInfo = connectivityManager.getActiveNetworkInfo(); } catch (SecurityException e) { // Expected if permission was revoked. return C.NETWORK_TYPE_UNKNOWN; } if (networkInfo == null || !networkInfo.isConnected()) { return C.NETWORK_TYPE_OFFLINE; } switch (networkInfo.getType()) { case ConnectivityManager.TYPE_WIFI: return C.NETWORK_TYPE_WIFI; case ConnectivityManager.TYPE_WIMAX: return C.NETWORK_TYPE_4G; case ConnectivityManager.TYPE_MOBILE: case ConnectivityManager.TYPE_MOBILE_DUN: case ConnectivityManager.TYPE_MOBILE_HIPRI: return getMobileNetworkType(networkInfo); case ConnectivityManager.TYPE_ETHERNET: return C.NETWORK_TYPE_ETHERNET; default: return C.NETWORK_TYPE_OTHER; } } private static @C.NetworkType int getMobileNetworkType(NetworkInfo networkInfo) { switch (networkInfo.getSubtype()) { case TelephonyManager.NETWORK_TYPE_EDGE: case TelephonyManager.NETWORK_TYPE_GPRS: return C.NETWORK_TYPE_2G; case TelephonyManager.NETWORK_TYPE_1xRTT: case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: case TelephonyManager.NETWORK_TYPE_HSDPA: case TelephonyManager.NETWORK_TYPE_HSPA: case TelephonyManager.NETWORK_TYPE_HSUPA: case TelephonyManager.NETWORK_TYPE_IDEN: case TelephonyManager.NETWORK_TYPE_UMTS: case TelephonyManager.NETWORK_TYPE_EHRPD: case TelephonyManager.NETWORK_TYPE_HSPAP: case TelephonyManager.NETWORK_TYPE_TD_SCDMA: return C.NETWORK_TYPE_3G; case TelephonyManager.NETWORK_TYPE_LTE: return C.NETWORK_TYPE_4G; case TelephonyManager.NETWORK_TYPE_NR: return Util.SDK_INT >= 29 ? C.NETWORK_TYPE_5G_SA : C.NETWORK_TYPE_UNKNOWN; case TelephonyManager.NETWORK_TYPE_IWLAN: return C.NETWORK_TYPE_WIFI; case TelephonyManager.NETWORK_TYPE_GSM: case TelephonyManager.NETWORK_TYPE_UNKNOWN: default: // Future mobile network types. return C.NETWORK_TYPE_CELLULAR_UNKNOWN; } } private final class Receiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @C.NetworkType int networkType = getNetworkTypeFromConnectivityManager(context); if (Util.SDK_INT >= 31 && networkType == C.NETWORK_TYPE_4G) { // Delay update of the network type to check whether this is actually 5G-NSA. Api31.disambiguate4gAnd5gNsa(context, /* instance= */ NetworkTypeObserver.this); } else { updateNetworkType(networkType); } } } @RequiresApi(31) private static final class Api31 { public static void disambiguate4gAnd5gNsa(Context context, NetworkTypeObserver instance) { try { TelephonyManager telephonyManager = checkNotNull((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)); DisplayInfoCallback callback = new DisplayInfoCallback(instance); telephonyManager.registerTelephonyCallback(context.getMainExecutor(), callback); // We are only interested in the initial response with the current state, so unregister // the listener immediately. telephonyManager.unregisterTelephonyCallback(callback); } catch (RuntimeException e) { // Ignore problems with listener registration and keep reporting as 4G. instance.updateNetworkType(C.NETWORK_TYPE_4G); } } private static final class DisplayInfoCallback extends TelephonyCallback implements DisplayInfoListener { private final NetworkTypeObserver instance; public DisplayInfoCallback(NetworkTypeObserver instance) { this.instance = instance; } @Override public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType(); boolean [MASK] = overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA || overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE || overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED; instance.updateNetworkType( [MASK] ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G); } } } } ","is5gNsa " "/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the ""License""); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.dubbo.remoting.http.restclient; import org.apache.dubbo.remoting.http.RequestTemplate; import org.apache.dubbo.remoting.http.RestClient; import org.apache.dubbo.remoting.http.RestResult; import org.apache.dubbo.remoting.http.config.HttpClientConfig; import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpTrace; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; public class HttpClientRestClient implements RestClient { private final CloseableHttpClient closeableHttpClient; private final HttpClientConfig httpClientConfig; public HttpClientRestClient(HttpClientConfig clientConfig) { closeableHttpClient = createHttpClient(); httpClientConfig = clientConfig; } @Override public CompletableFuture send(RequestTemplate requestTemplate) { HttpRequestBase httpRequest = null; String httpMethod = requestTemplate.getHttpMethod(); httpRequest = createHttpUriRequest(httpMethod, requestTemplate); if (httpRequest instanceof HttpEntityEnclosingRequest) { ((HttpEntityEnclosingRequestBase) httpRequest).setEntity(new ByteArrayEntity(requestTemplate.getSerializedBody())); } Map> allHeaders = requestTemplate.getAllHeaders(); allHeaders.remove(""Content-Length""); // header for (String headerName : allHeaders.keySet()) { Collection headerValues = allHeaders.get(headerName); for (String headerValue : headerValues) { httpRequest.addHeader(headerName, headerValue); } } httpRequest.setConfig(getRequestConfig(httpClientConfig)); CompletableFuture future = new CompletableFuture<>(); try { CloseableHttpResponse response = closeableHttpClient.execute(httpRequest); future.complete(new RestResult() { @Override public String getContentType() { Header header = response.getFirstHeader(""Content-Type""); return header == null ? null : header.getValue(); } @Override public byte[] getBody() throws IOException { if (response.getEntity() == null) { return new byte[0]; } return IOUtils.toByteArray(response.getEntity().getContent()); } @Override public Map> headers() { return Arrays.stream(response.getAllHeaders()).collect(Collectors.toMap(Header::getName, h -> Collections.singletonList(h.getValue()))); } @Override public byte[] getErrorResponse() throws IOException { return getBody(); } @Override public int getResponseCode() { return response.getStatusLine().getStatusCode(); } @Override public String getMessage() throws IOException { return appendErrorMessage(response.getStatusLine().getReasonPhrase(), new String(getErrorResponse())); } }); } catch (IOException e) { future.completeExceptionally(e); } return future; } private RequestConfig getRequestConfig(HttpClientConfig clientConfig) { // TODO config return RequestConfig.custom().build(); } @Override public void close() { try { closeableHttpClient.close(); } catch (IOException e) { } } @Override public void close(int timeout) { } @Override public boolean isClosed() { // TODO close judge return true; } public CloseableHttpClient createHttpClient() { PoolingHttpClientConnectionManager [MASK] = new PoolingHttpClientConnectionManager(); return HttpClients.custom().setConnectionManager( [MASK] ).build(); } protected HttpRequestBase createHttpUriRequest(String httpMethod, RequestTemplate requestTemplate) { String uri = requestTemplate.getURL(); HttpRequestBase httpUriRequest = null; if (HttpGet.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpGet(uri); } else if (HttpHead.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpHead(uri); } else if (HttpPost.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpPost(uri); } else if (HttpPut.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpPut(uri); } else if (HttpPatch.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpPatch(uri); } else if (HttpDelete.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpDelete(uri); } else if (HttpOptions.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpOptions(uri); } else if (HttpTrace.METHOD_NAME.equals(httpMethod)) { httpUriRequest = new HttpTrace(uri); } else { throw new IllegalArgumentException(""Invalid HTTP method: "" + httpMethod); } return httpUriRequest; } } ","connectionManager " "/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.ext.ima; import static com.google.android.exoplayer2.Player.COMMAND_GET_VOLUME; import static com.google.android.exoplayer2.ext.ima.ImaUtil.BITRATE_UNSET; import static com.google.android.exoplayer2.ext.ima.ImaUtil.TIMEOUT_UNSET; import static com.google.android.exoplayer2.ext.ima.ImaUtil.getAdGroupTimesUsForCuePoints; import static com.google.android.exoplayer2.ext.ima.ImaUtil.getImaLooper; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.msToUs; import static java.lang.Math.max; import static java.lang.annotation.ElementType.TYPE_USE; import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; import android.view.ViewGroup; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdError; import com.google.ads.interactivemedia.v3.api.AdErrorEvent; import com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener; import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener; import com.google.ads.interactivemedia.v3.api.AdEvent.AdEventType; import com.google.ads.interactivemedia.v3.api.AdPodInfo; import com.google.ads.interactivemedia.v3.api.AdsLoader; import com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener; import com.google.ads.interactivemedia.v3.api.AdsManager; import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent; import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings; import com.google.ads.interactivemedia.v3.api.AdsRequest; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo; import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdsLoader.EventListener; import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException; import com.google.android.exoplayer2.ui.AdOverlayInfo; import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Handles loading and playback of a single ad tag. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated /* package */ final class AdTagLoader implements Player.Listener { private static final String TAG = ""AdTagLoader""; private static final String IMA_SDK_SETTINGS_PLAYER_TYPE = ""google/exo.ext.ima""; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; /** * Interval at which ad progress updates are provided to the IMA SDK, in milliseconds. 200 ms is * the interval recommended by the Media Rating Council (MRC) for minimum polling of viewable * video impressions. * http://www.mediaratingcouncil.org/063014%20Viewable%20Ad%20Impression%20Guideline_Final.pdf. * * @see VideoAdPlayer.VideoAdPlayerCallback */ private static final int AD_PROGRESS_UPDATE_INTERVAL_MS = 200; /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ private static final long IMA_DURATION_UNSET = -1L; /** * Threshold before the end of content at which IMA is notified that content is complete if the * player buffers, in milliseconds. */ private static final long THRESHOLD_END_OF_CONTENT_MS = 5000; /** * Threshold before the start of an ad at which IMA is expected to be able to preload the ad, in * milliseconds. */ private static final long THRESHOLD_AD_PRELOAD_MS = 4000; /** The threshold below which ad cue points are treated as matching, in microseconds. */ private static final long THRESHOLD_AD_MATCH_US = 1000; /** The state of ad playback. */ @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) private @interface ImaAdState {} /** The ad playback state when IMA is not playing an ad. */ private static final int IMA_AD_STATE_NONE = 0; /** * The ad playback state when IMA has called {@link ComponentListener#playAd(AdMediaInfo)} and not * {@link ComponentListener##pauseAd(AdMediaInfo)}. */ private static final int IMA_AD_STATE_PLAYING = 1; /** * The ad playback state when IMA has called {@link ComponentListener#pauseAd(AdMediaInfo)} while * playing an ad. */ private static final int IMA_AD_STATE_PAUSED = 2; private final ImaUtil.Configuration configuration; private final ImaUtil.ImaFactory imaFactory; private final List supportedMimeTypes; private final DataSpec adTagDataSpec; private final Object adsId; private final Timeline.Period period; private final Handler handler; private final ComponentListener componentListener; private final List eventListeners; private final List adCallbacks; private final Runnable updateAdProgressRunnable; private final BiMap adInfoByAdMediaInfo; private final AdDisplayContainer adDisplayContainer; private final AdsLoader adsLoader; private final Runnable adLoadTimeoutRunnable; @Nullable private Object pendingAdRequestContext; @Nullable private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercent; @Nullable private AdsManager adsManager; private boolean isAdsManagerInitialized; @Nullable private AdLoadException pendingAdLoadError; private Timeline timeline; private long contentDurationMs; private AdPlaybackState adPlaybackState; private boolean released; // Fields tracking IMA's state. /** Whether IMA has sent an ad event to pause content since the last resume content event. */ private boolean imaPausedContent; /** The current ad playback state. */ private @ImaAdState int imaAdState; /** The current ad media info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. */ @Nullable private AdMediaInfo imaAdMediaInfo; /** The current ad info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. */ @Nullable private AdInfo imaAdInfo; /** Whether IMA has been notified that playback of content has finished. */ private boolean sentContentComplete; // Fields tracking the player/loader state. /** Whether the player is playing an ad. */ private boolean playingAd; /** Whether the player is buffering an ad. */ private boolean bufferingAd; /** * If the player is playing an ad, stores the ad index in its ad group. {@link C#INDEX_UNSET} * otherwise. */ private int playingAdIndexInAdGroup; /** * The ad info for a pending ad for which the media failed preparation, or {@code null} if no * pending ads have failed to prepare. */ @Nullable private AdInfo pendingAdPrepareErrorAdInfo; /** * If a content period has finished but IMA has not yet called {@link * ComponentListener#playAd(AdMediaInfo)}, stores the value of {@link * SystemClock#elapsedRealtime()} when the content stopped playing. This can be used to determine * a fake, increasing content position. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressElapsedRealtimeMs; /** * If {@link #fakeContentProgressElapsedRealtimeMs} is set, stores the offset from which the * content progress should increase. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressOffsetMs; /** Stores the pending content position when a seek operation was intercepted to play an ad. */ private long pendingContentPositionMs; /** * Whether {@link ComponentListener#getContentProgress()} has sent {@link * #pendingContentPositionMs} to IMA. */ private boolean sentPendingContentPositionMs; /** * Stores the real time in milliseconds at which the player started buffering, possibly due to not * having preloaded an ad, or {@link C#TIME_UNSET} if not applicable. */ private long waitingForPreloadElapsedRealtimeMs; /** Creates a new ad tag loader, starting the ad request if the ad tag is valid. */ @SuppressWarnings({""nullness:methodref.receiver.bound"", ""nullness:method.invocation""}) public AdTagLoader( Context context, ImaUtil.Configuration configuration, ImaUtil.ImaFactory imaFactory, List supportedMimeTypes, DataSpec adTagDataSpec, Object adsId, @Nullable ViewGroup adViewGroup) { this.configuration = configuration; this.imaFactory = imaFactory; @Nullable ImaSdkSettings imaSdkSettings = configuration.imaSdkSettings; if (imaSdkSettings == null) { imaSdkSettings = imaFactory.createImaSdkSettings(); if (configuration.debugModeEnabled) { imaSdkSettings.setDebugMode(true); } } imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE); imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION); this.supportedMimeTypes = supportedMimeTypes; this.adTagDataSpec = adTagDataSpec; this.adsId = adsId; period = new Timeline.Period(); handler = Util.createHandler(getImaLooper(), /* callback= */ null); componentListener = new ComponentListener(); eventListeners = new ArrayList<>(); adCallbacks = new ArrayList<>(/* initialCapacity= */ 1); if (configuration.applicationVideoAdPlayerCallback != null) { adCallbacks.add(configuration.applicationVideoAdPlayerCallback); } updateAdProgressRunnable = this::updateAdProgress; adInfoByAdMediaInfo = HashBiMap.create(); lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; fakeContentProgressOffsetMs = C.TIME_UNSET; pendingContentPositionMs = C.TIME_UNSET; waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET; contentDurationMs = C.TIME_UNSET; timeline = Timeline.EMPTY; adPlaybackState = AdPlaybackState.NONE; adLoadTimeoutRunnable = this::handleAdLoadTimeout; if (adViewGroup != null) { adDisplayContainer = imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener); } else { adDisplayContainer = imaFactory.createAudioAdDisplayContainer(context, /* player= */ componentListener); } if (configuration.companionAdSlots != null) { adDisplayContainer.setCompanionSlots(configuration.companionAdSlots); } adsLoader = requestAds(context, imaSdkSettings, adDisplayContainer); } /** Returns the underlying IMA SDK ads loader. */ public AdsLoader getAdsLoader() { return adsLoader; } /** Returns the IMA SDK ad display container. */ public AdDisplayContainer getAdDisplayContainer() { return adDisplayContainer; } /** Skips the current skippable ad, if there is one. */ public void skipAd() { if (adsManager != null) { adsManager.skip(); } } /** * Moves UI focus to the skip button (or other interactive elements), if currently shown. See * {@link AdsManager#focus()}. */ public void focusSkipButton() { if (adsManager != null) { adsManager.focus(); } } /** * Starts passing events from this instance (including any pending ad playback state) and * registers obstructions. */ public void addListenerWithAdView(EventListener eventListener, AdViewProvider adViewProvider) { boolean isStarted = !eventListeners.isEmpty(); eventListeners.add(eventListener); if (isStarted) { if (!AdPlaybackState.NONE.equals(adPlaybackState)) { // Pass the existing ad playback state to the new listener. eventListener.onAdPlaybackState(adPlaybackState); } return; } lastVolumePercent = 0; lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; maybeNotifyPendingAdLoadError(); if (!AdPlaybackState.NONE.equals(adPlaybackState)) { // Pass the ad playback state to the player, and resume ads if necessary. eventListener.onAdPlaybackState(adPlaybackState); } else if (adsManager != null) { adPlaybackState = new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints())); updateAdPlaybackState(); } for (AdOverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) { adDisplayContainer.registerFriendlyObstruction( imaFactory.createFriendlyObstruction( overlayInfo.view, ImaUtil.getFriendlyObstructionPurpose(overlayInfo.purpose), overlayInfo.reasonDetail)); } } /** * Populates the ad playback state with loaded cue points, if available. Any preroll will be * paused immediately while waiting for this instance to be {@link #activate(Player) activated}. */ public void maybePreloadAds(long contentPositionMs, long contentDurationMs) { maybeInitializeAdsManager(contentPositionMs, contentDurationMs); } /** Activates playback. */ public void activate(Player player) { this.player = player; player.addListener(this); boolean playWhenReady = player.getPlayWhenReady(); onTimelineChanged(player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); @Nullable AdsManager adsManager = this.adsManager; if (!AdPlaybackState.NONE.equals(adPlaybackState) && adsManager != null && imaPausedContent) { // Check whether the current ad break matches the expected ad break based on the current // position. If not, discard the current ad break so that the correct ad break can load. long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); int adGroupForPositionIndex = adPlaybackState.getAdGroupIndexForPositionUs( msToUs(contentPositionMs), msToUs(contentDurationMs)); if (adGroupForPositionIndex != C.INDEX_UNSET && imaAdInfo != null && imaAdInfo.adGroupIndex != adGroupForPositionIndex) { if (configuration.debugModeEnabled) { Log.d(TAG, ""Discarding preloaded ad "" + imaAdInfo); } adsManager.discardAdBreak(); } if (playWhenReady) { adsManager.resume(); } } } /** Deactivates playback. */ public void deactivate() { Player player = checkNotNull(this.player); if (!AdPlaybackState.NONE.equals(adPlaybackState) && imaPausedContent) { if (adsManager != null) { adsManager.pause(); } adPlaybackState = adPlaybackState.withAdResumePositionUs( playingAd ? msToUs(player.getCurrentPosition()) : 0); } lastVolumePercent = getPlayerVolumePercent(); lastAdProgress = getAdVideoProgressUpdate(); lastContentProgress = getContentVideoProgressUpdate(); player.removeListener(this); this.player = null; } /** Stops passing of events from this instance and unregisters obstructions. */ public void removeListener(EventListener eventListener) { eventListeners.remove(eventListener); if (eventListeners.isEmpty()) { adDisplayContainer.unregisterAllFriendlyObstructions(); } } /** Releases all resources used by the ad tag loader. */ public void release() { if (released) { return; } released = true; pendingAdRequestContext = null; destroyAdsManager(); adsLoader.removeAdsLoadedListener(componentListener); adsLoader.removeAdErrorListener(componentListener); if (configuration.applicationAdErrorListener != null) { adsLoader.removeAdErrorListener(configuration.applicationAdErrorListener); } adsLoader.release(); imaPausedContent = false; imaAdState = IMA_AD_STATE_NONE; imaAdMediaInfo = null; stopUpdatingAdProgress(); imaAdInfo = null; pendingAdLoadError = null; // No more ads will play once the loader is released, so mark all ad groups as skipped. for (int i = 0; i < adPlaybackState.adGroupCount; i++) { adPlaybackState = adPlaybackState.withSkippedAdGroup(i); } updateAdPlaybackState(); } /** Notifies the IMA SDK that the specified ad has been prepared for playback. */ public void handlePrepareComplete(int adGroupIndex, int adIndexInAdGroup) { AdInfo adInfo = new AdInfo(adGroupIndex, adIndexInAdGroup); if (configuration.debugModeEnabled) { Log.d(TAG, ""Prepared ad "" + adInfo); } @Nullable AdMediaInfo adMediaInfo = adInfoByAdMediaInfo.inverse().get(adInfo); if (adMediaInfo != null) { for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onLoaded(adMediaInfo); } } else { Log.w(TAG, ""Unexpected prepared ad "" + adInfo); } } /** Notifies the IMA SDK that the specified ad has failed to prepare for playback. */ public void handlePrepareError(int adGroupIndex, int adIndexInAdGroup, IOException exception) { if (player == null) { return; } try { handleAdPrepareError(adGroupIndex, adIndexInAdGroup, exception); } catch (RuntimeException e) { maybeNotifyInternalError(""handlePrepareError"", e); } } // Player.Listener implementation. @Override public void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) { if (timeline.isEmpty()) { // The player is being reset or contains no media. return; } this.timeline = timeline; Player player = checkNotNull(this.player); long contentDurationUs = timeline.getPeriod(player.getCurrentPeriodIndex(), period).durationUs; contentDurationMs = Util.usToMs(contentDurationUs); if (contentDurationUs != adPlaybackState.contentDurationUs) { adPlaybackState = adPlaybackState.withContentDurationUs(contentDurationUs); updateAdPlaybackState(); } long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); maybeInitializeAdsManager(contentPositionMs, contentDurationMs); handleTimelineOrPositionChanged(); } @Override public void onPositionDiscontinuity( Player.PositionInfo oldPosition, Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) { handleTimelineOrPositionChanged(); } @Override public void onPlaybackStateChanged(@Player.State int playbackState) { @Nullable Player player = this.player; if (adsManager == null || player == null) { return; } if (playbackState == Player.STATE_BUFFERING && !player.isPlayingAd() && isWaitingForFirstAdToPreload()) { waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime(); } else if (playbackState == Player.STATE_READY) { waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET; } handlePlayerStateChanged(player.getPlayWhenReady(), playbackState); } @Override public void onPlayWhenReadyChanged( boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) { if (adsManager == null || player == null) { return; } if (imaAdState == IMA_AD_STATE_PLAYING && !playWhenReady) { adsManager.pause(); return; } if (imaAdState == IMA_AD_STATE_PAUSED && playWhenReady) { adsManager.resume(); return; } handlePlayerStateChanged(playWhenReady, player.getPlaybackState()); } @Override public void onPlayerError(PlaybackException error) { if (imaAdState != IMA_AD_STATE_NONE) { AdMediaInfo adMediaInfo = checkNotNull(imaAdMediaInfo); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onError(adMediaInfo); } } } // Internal methods. private AdsLoader requestAds( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) { AdsLoader adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer); adsLoader.addAdErrorListener(componentListener); if (configuration.applicationAdErrorListener != null) { adsLoader.addAdErrorListener(configuration.applicationAdErrorListener); } adsLoader.addAdsLoadedListener(componentListener); AdsRequest request; try { request = ImaUtil.getAdsRequestForAdTagDataSpec(imaFactory, adTagDataSpec); } catch (IOException e) { adPlaybackState = new AdPlaybackState(adsId); updateAdPlaybackState(); pendingAdLoadError = AdLoadException.createForAllAds(e); maybeNotifyPendingAdLoadError(); return adsLoader; } pendingAdRequestContext = new Object(); request.setUserRequestContext(pendingAdRequestContext); if (configuration.enableContinuousPlayback != null) { request.setContinuousPlayback(configuration.enableContinuousPlayback); } if (configuration.vastLoadTimeoutMs != TIMEOUT_UNSET) { request.setVastLoadTimeout(configuration.vastLoadTimeoutMs); } request.setContentProgressProvider(componentListener); adsLoader.requestAds(request); return adsLoader; } private void maybeInitializeAdsManager(long contentPositionMs, long contentDurationMs) { @Nullable AdsManager adsManager = this.adsManager; if (!isAdsManagerInitialized && adsManager != null) { isAdsManagerInitialized = true; @Nullable AdsRenderingSettings adsRenderingSettings = setupAdsRendering(contentPositionMs, contentDurationMs); if (adsRenderingSettings == null) { // There are no ads to play. destroyAdsManager(); } else { adsManager.init(adsRenderingSettings); adsManager.start(); if (configuration.debugModeEnabled) { Log.d(TAG, ""Initialized with ads rendering settings: "" + adsRenderingSettings); } } updateAdPlaybackState(); } } /** * Configures ads rendering for starting playback, returning the settings for the IMA SDK or * {@code null} if no ads should play. */ @Nullable private AdsRenderingSettings setupAdsRendering(long contentPositionMs, long contentDurationMs) { AdsRenderingSettings adsRenderingSettings = imaFactory.createAdsRenderingSettings(); adsRenderingSettings.setEnablePreloading(true); adsRenderingSettings.setMimeTypes( configuration.adMediaMimeTypes != null ? configuration.adMediaMimeTypes : supportedMimeTypes); if (configuration.mediaLoadTimeoutMs != TIMEOUT_UNSET) { adsRenderingSettings.setLoadVideoTimeout(configuration.mediaLoadTimeoutMs); } if (configuration.mediaBitrate != BITRATE_UNSET) { adsRenderingSettings.setBitrateKbps(configuration.mediaBitrate / 1000); } adsRenderingSettings.setFocusSkipButtonWhenAvailable( configuration.focusSkipButtonWhenAvailable); if (configuration.adUiElements != null) { adsRenderingSettings.setUiElements(configuration.adUiElements); } // Skip ads based on the start position as required. int adGroupForPositionIndex = adPlaybackState.getAdGroupIndexForPositionUs( msToUs(contentPositionMs), msToUs(contentDurationMs)); if (adGroupForPositionIndex != C.INDEX_UNSET) { boolean playAdWhenStartingPlayback = adPlaybackState.getAdGroup(adGroupForPositionIndex).timeUs == msToUs(contentPositionMs) || configuration.playAdBeforeStartPosition; if (!playAdWhenStartingPlayback) { adGroupForPositionIndex++; } else if (hasMidrollAdGroups(adPlaybackState)) { // Provide the player's initial position to trigger loading and playing the ad. If there are // no midrolls, we are playing a preroll and any pending content position wouldn't be // cleared. pendingContentPositionMs = contentPositionMs; } if (adGroupForPositionIndex > 0) { for (int i = 0; i < adGroupForPositionIndex; i++) { adPlaybackState = adPlaybackState.withSkippedAdGroup(i); } if (adGroupForPositionIndex == adPlaybackState.adGroupCount) { // We don't need to play any ads. Because setPlayAdsAfterTime does not discard non-VMAP // ads, we signal that no ads will render so the caller can destroy the ads manager. return null; } long adGroupForPositionTimeUs = adPlaybackState.getAdGroup(adGroupForPositionIndex).timeUs; long adGroupBeforePositionTimeUs = adPlaybackState.getAdGroup(adGroupForPositionIndex - 1).timeUs; if (adGroupForPositionTimeUs == C.TIME_END_OF_SOURCE) { // Play the postroll by offsetting the start position just past the last non-postroll ad. adsRenderingSettings.setPlayAdsAfterTime( (double) adGroupBeforePositionTimeUs / C.MICROS_PER_SECOND + 1d); } else { // Play ads after the midpoint between the ad to play and the one before it, to avoid // issues with rounding one of the two ad times. double midpointTimeUs = (adGroupForPositionTimeUs + adGroupBeforePositionTimeUs) / 2d; adsRenderingSettings.setPlayAdsAfterTime(midpointTimeUs / C.MICROS_PER_SECOND); } } } return adsRenderingSettings; } private VideoProgressUpdate getContentVideoProgressUpdate() { boolean hasContentDuration = contentDurationMs != C.TIME_UNSET; long contentPositionMs; if (pendingContentPositionMs != C.TIME_UNSET) { sentPendingContentPositionMs = true; contentPositionMs = pendingContentPositionMs; } else if (player == null) { return lastContentProgress; } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; } else if (imaAdState == IMA_AD_STATE_NONE && !playingAd && hasContentDuration) { contentPositionMs = getContentPeriodPositionMs(player, timeline, period); } else { return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } long contentDurationMs = hasContentDuration ? this.contentDurationMs : IMA_DURATION_UNSET; return new VideoProgressUpdate(contentPositionMs, contentDurationMs); } private VideoProgressUpdate getAdVideoProgressUpdate() { if (player == null) { return lastAdProgress; } else if (imaAdState != IMA_AD_STATE_NONE && playingAd) { long adDuration = player.getDuration(); return adDuration == C.TIME_UNSET ? VideoProgressUpdate.VIDEO_TIME_NOT_READY : new VideoProgressUpdate(player.getCurrentPosition(), adDuration); } else { return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } } private void updateAdProgress() { VideoProgressUpdate videoProgressUpdate = getAdVideoProgressUpdate(); if (configuration.debugModeEnabled) { Log.d(TAG, ""Ad progress: "" + ImaUtil.getStringForVideoProgressUpdate(videoProgressUpdate)); } AdMediaInfo adMediaInfo = checkNotNull(imaAdMediaInfo); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onAdProgress(adMediaInfo, videoProgressUpdate); } handler.removeCallbacks(updateAdProgressRunnable); handler.postDelayed(updateAdProgressRunnable, AD_PROGRESS_UPDATE_INTERVAL_MS); } private void stopUpdatingAdProgress() { handler.removeCallbacks(updateAdProgressRunnable); } private int getPlayerVolumePercent() { @Nullable Player player = this.player; if (player == null) { return lastVolumePercent; } if (player.isCommandAvailable(COMMAND_GET_VOLUME)) { return (int) (player.getVolume() * 100); } // Check for a selected track using an audio renderer. return player.getCurrentTracks().isTypeSelected(C.TRACK_TYPE_AUDIO) ? 100 : 0; } private void handleAdEvent(AdEvent adEvent) { if (adsManager == null) { // Drop events after release. return; } switch (adEvent.getType()) { case AD_BREAK_FETCH_ERROR: String [MASK] String = checkNotNull(adEvent.getAdData().get(""adBreakTime"")); if (configuration.debugModeEnabled) { Log.d(TAG, ""Fetch error for ad at "" + [MASK] String + "" seconds""); } double [MASK] = Double.parseDouble( [MASK] String); int adGroupIndex = [MASK] == -1.0 ? adPlaybackState.adGroupCount - 1 : getAdGroupIndexForCuePointTimeSeconds( [MASK] ); markAdGroupInErrorStateAndClearPendingContentPosition(adGroupIndex); break; case CONTENT_PAUSE_REQUESTED: // After CONTENT_PAUSE_REQUESTED, IMA will playAd/pauseAd/stopAd to show one or more ads // before sending CONTENT_RESUME_REQUESTED. imaPausedContent = true; pauseContentInternal(); break; case TAPPED: for (int i = 0; i < eventListeners.size(); i++) { eventListeners.get(i).onAdTapped(); } break; case CLICKED: for (int i = 0; i < eventListeners.size(); i++) { eventListeners.get(i).onAdClicked(); } break; case CONTENT_RESUME_REQUESTED: imaPausedContent = false; resumeContentInternal(); break; case LOG: Map adData = adEvent.getAdData(); String message = ""AdEvent: "" + adData; Log.i(TAG, message); break; default: break; } } private void pauseContentInternal() { imaAdState = IMA_AD_STATE_NONE; if (sentPendingContentPositionMs) { pendingContentPositionMs = C.TIME_UNSET; sentPendingContentPositionMs = false; } } private void resumeContentInternal() { if (imaAdInfo != null) { adPlaybackState = adPlaybackState.withSkippedAdGroup(imaAdInfo.adGroupIndex); updateAdPlaybackState(); } } /** * Returns whether this instance is expecting the first ad in an the upcoming ad group to load * within the {@link ImaUtil.Configuration#adPreloadTimeoutMs preload timeout}. */ private boolean isWaitingForFirstAdToPreload() { @Nullable Player player = this.player; if (player == null) { return false; } int adGroupIndex = getLoadingAdGroupIndex(); if (adGroupIndex == C.INDEX_UNSET) { return false; } AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); if (adGroup.count != C.LENGTH_UNSET && adGroup.count != 0 && adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) { // An ad is available already. return false; } long adGroupTimeMs = Util.usToMs(adGroup.timeUs); long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); long timeUntilAdMs = adGroupTimeMs - contentPositionMs; return timeUntilAdMs < configuration.adPreloadTimeoutMs; } private boolean isWaitingForCurrentAdToLoad() { @Nullable Player player = this.player; if (player == null) { return false; } int adGroupIndex = player.getCurrentAdGroupIndex(); if (adGroupIndex == C.INDEX_UNSET) { return false; } AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); int adIndexInAdGroup = player.getCurrentAdIndexInAdGroup(); if (adGroup.count == C.LENGTH_UNSET || adGroup.count <= adIndexInAdGroup) { return true; } return adGroup.states[adIndexInAdGroup] == AdPlaybackState.AD_STATE_UNAVAILABLE; } private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { if (playingAd && imaAdState == IMA_AD_STATE_PLAYING) { if (!bufferingAd && playbackState == Player.STATE_BUFFERING) { bufferingAd = true; AdMediaInfo adMediaInfo = checkNotNull(imaAdMediaInfo); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onBuffering(adMediaInfo); } stopUpdatingAdProgress(); } else if (bufferingAd && playbackState == Player.STATE_READY) { bufferingAd = false; updateAdProgress(); } } if (imaAdState == IMA_AD_STATE_NONE && playbackState == Player.STATE_BUFFERING && playWhenReady) { ensureSentContentCompleteIfAtEndOfStream(); } else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) { @Nullable AdMediaInfo adMediaInfo = imaAdMediaInfo; if (adMediaInfo == null) { Log.w(TAG, ""onEnded without ad media info""); } else { for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onEnded(adMediaInfo); } } if (configuration.debugModeEnabled) { Log.d(TAG, ""VideoAdPlayerCallback.onEnded in onPlaybackStateChanged""); } } } private void handleTimelineOrPositionChanged() { @Nullable Player player = this.player; if (adsManager == null || player == null) { return; } if (!playingAd && !player.isPlayingAd()) { ensureSentContentCompleteIfAtEndOfStream(); if (!sentContentComplete && !timeline.isEmpty()) { long positionMs = getContentPeriodPositionMs(player, timeline, period); timeline.getPeriod(player.getCurrentPeriodIndex(), period); int newAdGroupIndex = period.getAdGroupIndexForPositionUs(msToUs(positionMs)); if (newAdGroupIndex != C.INDEX_UNSET) { sentPendingContentPositionMs = false; pendingContentPositionMs = positionMs; } } } boolean wasPlayingAd = playingAd; int oldPlayingAdIndexInAdGroup = playingAdIndexInAdGroup; playingAd = player.isPlayingAd(); playingAdIndexInAdGroup = playingAd ? player.getCurrentAdIndexInAdGroup() : C.INDEX_UNSET; boolean adFinished = wasPlayingAd && playingAdIndexInAdGroup != oldPlayingAdIndexInAdGroup; if (adFinished) { // IMA is waiting for the ad playback to finish so invoke the callback now. // Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again. @Nullable AdMediaInfo adMediaInfo = imaAdMediaInfo; if (adMediaInfo == null) { Log.w(TAG, ""onEnded without ad media info""); } else { @Nullable AdInfo adInfo = adInfoByAdMediaInfo.get(adMediaInfo); if (playingAdIndexInAdGroup == C.INDEX_UNSET || (adInfo != null && adInfo.adIndexInAdGroup < playingAdIndexInAdGroup)) { for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onEnded(adMediaInfo); } if (configuration.debugModeEnabled) { Log.d( TAG, ""VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity""); } } } } if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(player.getCurrentAdGroupIndex()); if (adGroup.timeUs == C.TIME_END_OF_SOURCE) { sendContentComplete(); } else { // IMA hasn't called playAd yet, so fake the content position. fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); fakeContentProgressOffsetMs = Util.usToMs(adGroup.timeUs); if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { fakeContentProgressOffsetMs = contentDurationMs; } } } if (isWaitingForCurrentAdToLoad()) { handler.removeCallbacks(adLoadTimeoutRunnable); handler.postDelayed(adLoadTimeoutRunnable, configuration.adPreloadTimeoutMs); } } private void loadAdInternal(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) { if (adsManager == null) { // Drop events after release. if (configuration.debugModeEnabled) { Log.d( TAG, ""loadAd after release "" + getAdMediaInfoString(adMediaInfo) + "", ad pod "" + adPodInfo); } return; } int adGroupIndex = getAdGroupIndexForAdPod(adPodInfo); int adIndexInAdGroup = adPodInfo.getAdPosition() - 1; AdInfo adInfo = new AdInfo(adGroupIndex, adIndexInAdGroup); // The ad URI may already be known, so force put to update it if needed. adInfoByAdMediaInfo.forcePut(adMediaInfo, adInfo); if (configuration.debugModeEnabled) { Log.d(TAG, ""loadAd "" + getAdMediaInfoString(adMediaInfo)); } if (adPlaybackState.isAdInErrorState(adGroupIndex, adIndexInAdGroup)) { // We have already marked this ad as having failed to load, so ignore the request. IMA will // timeout after its media load timeout. return; } if (player != null && player.getCurrentAdGroupIndex() == adGroupIndex && player.getCurrentAdIndexInAdGroup() == adIndexInAdGroup) { // Loaded ad info the player is currently waiting for. handler.removeCallbacks(adLoadTimeoutRunnable); } // The ad count may increase on successive loads of ads in the same ad pod, for example, due to // separate requests for ad tags with multiple ads within the ad pod completing after an earlier // ad has loaded. See also https://github.com/google/ExoPlayer/issues/7477. AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adInfo.adGroupIndex); adPlaybackState = adPlaybackState.withAdCount( adInfo.adGroupIndex, max(adPodInfo.getTotalAds(), adGroup.states.length)); adGroup = adPlaybackState.getAdGroup(adInfo.adGroupIndex); for (int i = 0; i < adIndexInAdGroup; i++) { // Any preceding ads that haven't loaded are not going to load. if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) { adPlaybackState = adPlaybackState.withAdLoadError(adGroupIndex, /* adIndexInAdGroup= */ i); } } Uri adUri = Uri.parse(adMediaInfo.getUrl()); adPlaybackState = adPlaybackState.withAvailableAdUri(adInfo.adGroupIndex, adInfo.adIndexInAdGroup, adUri); updateAdPlaybackState(); } private void playAdInternal(AdMediaInfo adMediaInfo) { if (configuration.debugModeEnabled) { Log.d(TAG, ""playAd "" + getAdMediaInfoString(adMediaInfo)); } if (adsManager == null) { // Drop events after release. return; } if (imaAdState == IMA_AD_STATE_PLAYING) { // IMA does not always call stopAd before resuming content. // See [Internal: b/38354028]. Log.w(TAG, ""Unexpected playAd without stopAd""); } if (imaAdState == IMA_AD_STATE_NONE) { // IMA is requesting to play the ad, so stop faking the content position. fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; fakeContentProgressOffsetMs = C.TIME_UNSET; imaAdState = IMA_AD_STATE_PLAYING; imaAdMediaInfo = adMediaInfo; imaAdInfo = checkNotNull(adInfoByAdMediaInfo.get(adMediaInfo)); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onPlay(adMediaInfo); } if (pendingAdPrepareErrorAdInfo != null && pendingAdPrepareErrorAdInfo.equals(imaAdInfo)) { pendingAdPrepareErrorAdInfo = null; for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onError(adMediaInfo); } } updateAdProgress(); } else { imaAdState = IMA_AD_STATE_PLAYING; checkState(adMediaInfo.equals(imaAdMediaInfo)); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onResume(adMediaInfo); } } if (player == null || !player.getPlayWhenReady()) { // Either this loader hasn't been activated yet, or the player is paused now. checkNotNull(adsManager).pause(); } } private void pauseAdInternal(AdMediaInfo adMediaInfo) { if (configuration.debugModeEnabled) { Log.d(TAG, ""pauseAd "" + getAdMediaInfoString(adMediaInfo)); } if (adsManager == null) { // Drop event after release. return; } if (imaAdState == IMA_AD_STATE_NONE) { // This method is called if loadAd has been called but the loaded ad won't play due to a seek // to a different position, so drop the event. See also [Internal: b/159111848]. return; } if (configuration.debugModeEnabled && !adMediaInfo.equals(imaAdMediaInfo)) { Log.w( TAG, ""Unexpected pauseAd for "" + getAdMediaInfoString(adMediaInfo) + "", expected "" + getAdMediaInfoString(imaAdMediaInfo)); } imaAdState = IMA_AD_STATE_PAUSED; for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onPause(adMediaInfo); } } private void stopAdInternal(AdMediaInfo adMediaInfo) { if (configuration.debugModeEnabled) { Log.d(TAG, ""stopAd "" + getAdMediaInfoString(adMediaInfo)); } if (adsManager == null) { // Drop event after release. return; } if (imaAdState == IMA_AD_STATE_NONE) { // This method is called if loadAd has been called but the preloaded ad won't play due to a // seek to a different position, so drop the event and discard the ad. See also [Internal: // b/159111848]. @Nullable AdInfo adInfo = adInfoByAdMediaInfo.get(adMediaInfo); if (adInfo != null) { adPlaybackState = adPlaybackState.withSkippedAd(adInfo.adGroupIndex, adInfo.adIndexInAdGroup); updateAdPlaybackState(); } return; } imaAdState = IMA_AD_STATE_NONE; stopUpdatingAdProgress(); // TODO: Handle the skipped event so the ad can be marked as skipped rather than played. checkNotNull(imaAdInfo); int adGroupIndex = imaAdInfo.adGroupIndex; int adIndexInAdGroup = imaAdInfo.adIndexInAdGroup; if (adPlaybackState.isAdInErrorState(adGroupIndex, adIndexInAdGroup)) { // We have already marked this ad as having failed to load, so ignore the request. return; } adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, adIndexInAdGroup).withAdResumePositionUs(0); updateAdPlaybackState(); if (!playingAd) { imaAdMediaInfo = null; imaAdInfo = null; } } private void handleAdGroupLoadError(Exception error) { int adGroupIndex = getLoadingAdGroupIndex(); if (adGroupIndex == C.INDEX_UNSET) { Log.w(TAG, ""Unable to determine ad group index for ad group load error"", error); return; } markAdGroupInErrorStateAndClearPendingContentPosition(adGroupIndex); if (pendingAdLoadError == null) { pendingAdLoadError = AdLoadException.createForAdGroup(error, adGroupIndex); } } private void handleAdLoadTimeout() { // IMA got stuck and didn't load an ad in time, so skip the entire group. handleAdGroupLoadError(new IOException(""Ad loading timed out"")); maybeNotifyPendingAdLoadError(); } private void markAdGroupInErrorStateAndClearPendingContentPosition(int adGroupIndex) { // Update the ad playback state so all ads in the ad group are in the error state. AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); if (adGroup.count == C.LENGTH_UNSET) { adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, max(1, adGroup.states.length)); adGroup = adPlaybackState.getAdGroup(adGroupIndex); } for (int i = 0; i < adGroup.count; i++) { if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) { if (configuration.debugModeEnabled) { Log.d(TAG, ""Removing ad "" + i + "" in ad group "" + adGroupIndex); } adPlaybackState = adPlaybackState.withAdLoadError(adGroupIndex, i); } } updateAdPlaybackState(); // Clear any pending content position that triggered attempting to load the ad group. pendingContentPositionMs = C.TIME_UNSET; fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; } private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) { if (configuration.debugModeEnabled) { Log.d( TAG, ""Prepare error for ad "" + adIndexInAdGroup + "" in group "" + adGroupIndex, exception); } if (adsManager == null) { Log.w(TAG, ""Ignoring ad prepare error after release""); return; } if (imaAdState == IMA_AD_STATE_NONE) { // Send IMA a content position at the ad group so that it will try to play it, at which point // we can notify that it failed to load. fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); fakeContentProgressOffsetMs = Util.usToMs(adPlaybackState.getAdGroup(adGroupIndex).timeUs); if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { fakeContentProgressOffsetMs = contentDurationMs; } pendingAdPrepareErrorAdInfo = new AdInfo(adGroupIndex, adIndexInAdGroup); } else { AdMediaInfo adMediaInfo = checkNotNull(imaAdMediaInfo); // We're already playing an ad. if (adIndexInAdGroup > playingAdIndexInAdGroup) { // Mark the playing ad as ended so we can notify the error on the next ad and remove it, // which means that the ad after will load (if any). for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onEnded(adMediaInfo); } } playingAdIndexInAdGroup = adPlaybackState.getAdGroup(adGroupIndex).getFirstAdIndexToPlay(); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onError(checkNotNull(adMediaInfo)); } } adPlaybackState = adPlaybackState.withAdLoadError(adGroupIndex, adIndexInAdGroup); updateAdPlaybackState(); } private void ensureSentContentCompleteIfAtEndOfStream() { if (sentContentComplete || contentDurationMs == C.TIME_UNSET || pendingContentPositionMs != C.TIME_UNSET) { return; } long contentPeriodPositionMs = getContentPeriodPositionMs(checkNotNull(player), timeline, period); if (contentPeriodPositionMs + THRESHOLD_END_OF_CONTENT_MS < contentDurationMs) { return; } int pendingAdGroupIndex = adPlaybackState.getAdGroupIndexForPositionUs( msToUs(contentPeriodPositionMs), msToUs(contentDurationMs)); if (pendingAdGroupIndex != C.INDEX_UNSET && adPlaybackState.getAdGroup(pendingAdGroupIndex).timeUs != C.TIME_END_OF_SOURCE && adPlaybackState.getAdGroup(pendingAdGroupIndex).shouldPlayAdGroup()) { // Pending mid-roll ad that needs to be played before marking the content complete. return; } sendContentComplete(); } private void sendContentComplete() { for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onContentComplete(); } sentContentComplete = true; if (configuration.debugModeEnabled) { Log.d(TAG, ""adsLoader.contentComplete""); } for (int i = 0; i < adPlaybackState.adGroupCount; i++) { if (adPlaybackState.getAdGroup(i).timeUs != C.TIME_END_OF_SOURCE) { adPlaybackState = adPlaybackState.withSkippedAdGroup(/* adGroupIndex= */ i); } } updateAdPlaybackState(); } private void updateAdPlaybackState() { for (int i = 0; i < eventListeners.size(); i++) { eventListeners.get(i).onAdPlaybackState(adPlaybackState); } } private void maybeNotifyPendingAdLoadError() { if (pendingAdLoadError != null) { for (int i = 0; i < eventListeners.size(); i++) { eventListeners.get(i).onAdLoadError(pendingAdLoadError, adTagDataSpec); } pendingAdLoadError = null; } } private void maybeNotifyInternalError(String name, Exception cause) { String message = ""Internal error in "" + name; Log.e(TAG, message, cause); // We can't recover from an unexpected error in general, so skip all remaining ads. for (int i = 0; i < adPlaybackState.adGroupCount; i++) { adPlaybackState = adPlaybackState.withSkippedAdGroup(i); } updateAdPlaybackState(); for (int i = 0; i < eventListeners.size(); i++) { eventListeners .get(i) .onAdLoadError( AdLoadException.createForUnexpected(new RuntimeException(message, cause)), adTagDataSpec); } } private int getAdGroupIndexForAdPod(AdPodInfo adPodInfo) { if (adPodInfo.getPodIndex() == -1) { // This is a postroll ad. return adPlaybackState.adGroupCount - 1; } // adPodInfo.podIndex may be 0-based or 1-based, so for now look up the cue point instead. return getAdGroupIndexForCuePointTimeSeconds(adPodInfo.getTimeOffset()); } /** * Returns the index of the ad group that will preload next, or {@link C#INDEX_UNSET} if there is * no such ad group. */ private int getLoadingAdGroupIndex() { if (player == null) { return C.INDEX_UNSET; } long playerPositionUs = msToUs(getContentPeriodPositionMs(player, timeline, period)); int adGroupIndex = adPlaybackState.getAdGroupIndexForPositionUs(playerPositionUs, msToUs(contentDurationMs)); if (adGroupIndex == C.INDEX_UNSET) { adGroupIndex = adPlaybackState.getAdGroupIndexAfterPositionUs( playerPositionUs, msToUs(contentDurationMs)); } return adGroupIndex; } private int getAdGroupIndexForCuePointTimeSeconds(double cuePointTimeSeconds) { // We receive initial cue points from IMA SDK as floats. This code replicates the same // calculation used to populate adGroupTimesUs (having truncated input back to float, to avoid // failures if the behavior of the IMA SDK changes to provide greater precision). float cuePointTimeSecondsFloat = (float) cuePointTimeSeconds; long adPodTimeUs = Math.round((double) cuePointTimeSecondsFloat * C.MICROS_PER_SECOND); for (int adGroupIndex = 0; adGroupIndex < adPlaybackState.adGroupCount; adGroupIndex++) { long adGroupTimeUs = adPlaybackState.getAdGroup(adGroupIndex).timeUs; if (adGroupTimeUs != C.TIME_END_OF_SOURCE && Math.abs(adGroupTimeUs - adPodTimeUs) < THRESHOLD_AD_MATCH_US) { return adGroupIndex; } } throw new IllegalStateException(""Failed to find cue point""); } private String getAdMediaInfoString(@Nullable AdMediaInfo adMediaInfo) { @Nullable AdInfo adInfo = adInfoByAdMediaInfo.get(adMediaInfo); return ""AdMediaInfo["" + (adMediaInfo == null ? ""null"" : adMediaInfo.getUrl()) + "", "" + adInfo + ""]""; } private static long getContentPeriodPositionMs( Player player, Timeline timeline, Timeline.Period period) { long contentWindowPositionMs = player.getContentPosition(); if (timeline.isEmpty()) { return contentWindowPositionMs; } else { return contentWindowPositionMs - timeline.getPeriod(player.getCurrentPeriodIndex(), period).getPositionInWindowMs(); } } private static boolean hasMidrollAdGroups(AdPlaybackState adPlaybackState) { int count = adPlaybackState.adGroupCount; if (count == 1) { long adGroupTimeUs = adPlaybackState.getAdGroup(0).timeUs; return adGroupTimeUs != 0 && adGroupTimeUs != C.TIME_END_OF_SOURCE; } else if (count == 2) { return adPlaybackState.getAdGroup(0).timeUs != 0 || adPlaybackState.getAdGroup(1).timeUs != C.TIME_END_OF_SOURCE; } else { // There's at least one midroll ad group, as adPlaybackState is never empty. return true; } } private void destroyAdsManager() { if (adsManager != null) { adsManager.removeAdErrorListener(componentListener); if (configuration.applicationAdErrorListener != null) { adsManager.removeAdErrorListener(configuration.applicationAdErrorListener); } adsManager.removeAdEventListener(componentListener); if (configuration.applicationAdEventListener != null) { adsManager.removeAdEventListener(configuration.applicationAdEventListener); } adsManager.destroy(); adsManager = null; } } private final class ComponentListener implements AdsLoadedListener, ContentProgressProvider, AdEventListener, AdErrorListener, VideoAdPlayer { // AdsLoader.AdsLoadedListener implementation. @Override public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) { AdsManager adsManager = adsManagerLoadedEvent.getAdsManager(); if (!Util.areEqual(pendingAdRequestContext, adsManagerLoadedEvent.getUserRequestContext())) { adsManager.destroy(); return; } pendingAdRequestContext = null; AdTagLoader.this.adsManager = adsManager; adsManager.addAdErrorListener(this); if (configuration.applicationAdErrorListener != null) { adsManager.addAdErrorListener(configuration.applicationAdErrorListener); } adsManager.addAdEventListener(this); if (configuration.applicationAdEventListener != null) { adsManager.addAdEventListener(configuration.applicationAdEventListener); } try { adPlaybackState = new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints())); updateAdPlaybackState(); } catch (RuntimeException e) { maybeNotifyInternalError(""onAdsManagerLoaded"", e); } } // ContentProgressProvider implementation. @Override public VideoProgressUpdate getContentProgress() { VideoProgressUpdate videoProgressUpdate = getContentVideoProgressUpdate(); if (configuration.debugModeEnabled) { Log.d( TAG, ""Content progress: "" + ImaUtil.getStringForVideoProgressUpdate(videoProgressUpdate)); } if (waitingForPreloadElapsedRealtimeMs != C.TIME_UNSET) { // IMA is polling the player position but we are buffering for an ad to preload, so playback // may be stuck. Detect this case and signal an error if applicable. long stuckElapsedRealtimeMs = SystemClock.elapsedRealtime() - waitingForPreloadElapsedRealtimeMs; if (stuckElapsedRealtimeMs >= THRESHOLD_AD_PRELOAD_MS) { waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET; handleAdGroupLoadError(new IOException(""Ad preloading timed out"")); maybeNotifyPendingAdLoadError(); } } else if (pendingContentPositionMs != C.TIME_UNSET && player != null && player.getPlaybackState() == Player.STATE_BUFFERING && isWaitingForFirstAdToPreload()) { // Prepare to timeout the load of an ad for the pending seek operation. waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime(); } return videoProgressUpdate; } // AdEvent.AdEventListener implementation. @Override public void onAdEvent(AdEvent adEvent) { AdEventType adEventType = adEvent.getType(); if (configuration.debugModeEnabled && adEventType != AdEventType.AD_PROGRESS) { Log.d(TAG, ""onAdEvent: "" + adEventType); } try { handleAdEvent(adEvent); } catch (RuntimeException e) { maybeNotifyInternalError(""onAdEvent"", e); } } // AdErrorEvent.AdErrorListener implementation. @Override public void onAdError(AdErrorEvent adErrorEvent) { AdError error = adErrorEvent.getError(); if (configuration.debugModeEnabled) { Log.d(TAG, ""onAdError"", error); } if (adsManager == null) { // No ads were loaded, so allow playback to start without any ads. pendingAdRequestContext = null; adPlaybackState = new AdPlaybackState(adsId); updateAdPlaybackState(); } else if (ImaUtil.isAdGroupLoadError(error)) { try { handleAdGroupLoadError(error); } catch (RuntimeException e) { maybeNotifyInternalError(""onAdError"", e); } } if (pendingAdLoadError == null) { pendingAdLoadError = AdLoadException.createForAllAds(error); } maybeNotifyPendingAdLoadError(); } // VideoAdPlayer implementation. @Override public void addCallback(VideoAdPlayerCallback videoAdPlayerCallback) { adCallbacks.add(videoAdPlayerCallback); } @Override public void removeCallback(VideoAdPlayerCallback videoAdPlayerCallback) { adCallbacks.remove(videoAdPlayerCallback); } @Override public VideoProgressUpdate getAdProgress() { throw new IllegalStateException(""Unexpected call to getAdProgress when using preloading""); } @Override public int getVolume() { return getPlayerVolumePercent(); } @Override public void loadAd(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) { try { loadAdInternal(adMediaInfo, adPodInfo); } catch (RuntimeException e) { maybeNotifyInternalError(""loadAd"", e); } } @Override public void playAd(AdMediaInfo adMediaInfo) { try { playAdInternal(adMediaInfo); } catch (RuntimeException e) { maybeNotifyInternalError(""playAd"", e); } } @Override public void pauseAd(AdMediaInfo adMediaInfo) { try { pauseAdInternal(adMediaInfo); } catch (RuntimeException e) { maybeNotifyInternalError(""pauseAd"", e); } } @Override public void stopAd(AdMediaInfo adMediaInfo) { try { stopAdInternal(adMediaInfo); } catch (RuntimeException e) { maybeNotifyInternalError(""stopAd"", e); } } @Override public void release() { // Do nothing. } } // TODO: Consider moving this into AdPlaybackState. private static final class AdInfo { public final int adGroupIndex; public final int adIndexInAdGroup; public AdInfo(int adGroupIndex, int adIndexInAdGroup) { this.adGroupIndex = adGroupIndex; this.adIndexInAdGroup = adIndexInAdGroup; } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } AdInfo adInfo = (AdInfo) o; if (adGroupIndex != adInfo.adGroupIndex) { return false; } return adIndexInAdGroup == adInfo.adIndexInAdGroup; } @Override public int hashCode() { int result = adGroupIndex; result = 31 * result + adIndexInAdGroup; return result; } @Override public String toString() { return ""("" + adGroupIndex + "", "" + adIndexInAdGroup + ')'; } } } ","adGroupTimeSeconds " "/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.audio; import static java.lang.annotation.ElementType.TYPE_USE; import android.media.AudioDeviceInfo; import android.media.AudioTrack; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.analytics.PlayerId; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.nio.ByteBuffer; /** * A sink that consumes audio data. * *

    Before starting playback, specify the input audio format by calling {@link #configure(Format, * int, int[])}. * *

    Call {@link #handleBuffer(ByteBuffer, long, int)} to write data, and {@link * #handleDiscontinuity()} when the data being fed is discontinuous. Call {@link #play()} to start * playing the written data. * *

    Call {@link #configure(Format, int, int[])} whenever the input format changes. The sink will * be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, long, int)}. * *

    Call {@link #flush()} to prepare the sink to receive audio data from a new playback position. * *

    Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers * will be provided via {@link #handleBuffer(ByteBuffer, long, int)} until the next {@link * #flush()}. Call {@link #reset()} when the instance is no longer required. * *

    The implementation may be backed by a platform {@link AudioTrack}. In this case, {@link * #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, {@link * #enableTunnelingV21()} and {@link #disableTunneling()} may be called before writing data to the * sink. These methods may also be called after writing data to the sink, in which case it will be * reinitialized as required. For implementations that are not based on platform {@link * AudioTrack}s, calling methods relating to audio sessions, audio attributes, and tunneling may * have no effect. * * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which * contains the same ExoPlayer code). See the * migration guide for more details, including a script to help with the migration. */ @Deprecated public interface AudioSink { /** Listener for audio sink events. */ interface Listener { /** * Called when the audio sink handles a buffer whose timestamp is discontinuous with the last * buffer handled since it was reset. */ void onPositionDiscontinuity(); /** * Called when the audio sink's position has increased for the first time since it was last * paused or flushed. * * @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at * which playout started. Only valid if the audio track has not underrun. */ default void onPositionAdvancing(long playoutStartSystemTimeMs) {} /** * Called when the audio sink runs out of data. * *

    An audio sink implementation may never call this method (for example, if audio data is * consumed in batches rather than based on the sink's own clock). * * @param bufferSize The size of the sink's buffer, in bytes. * @param bufferSizeMs The size of the sink's buffer, in milliseconds, if it is configured for * PCM output. {@link C#TIME_UNSET} if it is configured for encoded audio output, as the * buffered media can have a variable bitrate so the duration may be unknown. * @param elapsedSinceLastFeedMs The time since the sink was last fed data, in milliseconds. */ void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); /** * Called when skipping silences is enabled or disabled. * * @param [MASK] Whether skipping silences is enabled. */ void onSkipSilenceEnabledChanged(boolean [MASK] ); /** Called when the offload buffer has been partially emptied. */ default void onOffloadBufferEmptying() {} /** Called when the offload buffer has been filled completely. */ default void onOffloadBufferFull() {} /** * Called when {@link AudioSink} has encountered an error. * *

    If the sink writes to a platform {@link AudioTrack}, this will called for all {@link * AudioTrack} errors. * *

    This method being called does not indicate that playback has failed, or that it will fail. * The player may be able to recover from the error (for example by recreating the AudioTrack, * possibly with different settings) and continue. Hence applications should not * implement this method to display a user visible error or initiate an application level retry * ({@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior). * This method is called to provide the application with an opportunity to log the error if it * wishes to do so. * *

    Fatal errors that cannot be recovered will be reported wrapped in a {@link * ExoPlaybackException} by {@link Player.Listener#onPlayerError(PlaybackException)}. * * @param audioSinkError The error that occurred. Typically an {@link InitializationException}, * a {@link WriteException}, or an {@link UnexpectedDiscontinuityException}. */ default void onAudioSinkError(Exception audioSinkError) {} /** Called when audio capabilities changed. */ default void onAudioCapabilitiesChanged() {} } /** Thrown when a failure occurs configuring the sink. */ final class ConfigurationException extends Exception { /** Input {@link Format} of the sink when the configuration failure occurs. */ public final Format format; /** Creates a new configuration exception with the specified {@code cause} and no message. */ public ConfigurationException(Throwable cause, Format format) { super(cause); this.format = format; } /** Creates a new configuration exception with the specified {@code message} and no cause. */ public ConfigurationException(String message, Format format) { super(message); this.format = format; } } /** Thrown when a failure occurs initializing the sink. */ final class InitializationException extends Exception { /** The underlying {@link AudioTrack}'s state. */ public final int audioTrackState; /** If the exception can be recovered by recreating the sink. */ public final boolean isRecoverable; /** The input {@link Format} of the sink when the error occurs. */ public final Format format; /** * Creates a new instance. * * @param audioTrackState The underlying {@link AudioTrack}'s state. * @param sampleRate The requested sample rate in Hz. * @param channelConfig The requested channel configuration. * @param bufferSize The requested buffer size in bytes. * @param format The input format of the sink when the error occurs. * @param isRecoverable Whether the exception can be recovered by recreating the sink. * @param audioTrackException Exception thrown during the creation of the {@link AudioTrack}. */ public InitializationException( int audioTrackState, int sampleRate, int channelConfig, int bufferSize, Format format, boolean isRecoverable, @Nullable Exception audioTrackException) { super( ""AudioTrack init failed "" + audioTrackState + "" "" + (""Config("" + sampleRate + "", "" + channelConfig + "", "" + bufferSize + "")"") + "" "" + format + (isRecoverable ? "" (recoverable)"" : """"), audioTrackException); this.audioTrackState = audioTrackState; this.isRecoverable = isRecoverable; this.format = format; } } /** Thrown when a failure occurs writing to the sink. */ final class WriteException extends Exception { /** * The error value returned from the sink implementation. If the sink writes to a platform * {@link AudioTrack}, this will be the error value returned from {@link * AudioTrack#write(byte[], int, int)} or {@link AudioTrack#write(ByteBuffer, int, int)}. * Otherwise, the meaning of the error code depends on the sink implementation. */ public final int errorCode; /** If the exception can be recovered by recreating the sink. */ public final boolean isRecoverable; /** The input {@link Format} of the sink when the error occurs. */ public final Format format; /** * Creates an instance. * * @param errorCode The error value returned from the sink implementation. * @param format The input format of the sink when the error occurs. * @param isRecoverable Whether the exception can be recovered by recreating the sink. */ public WriteException(int errorCode, Format format, boolean isRecoverable) { super(""AudioTrack write failed: "" + errorCode); this.isRecoverable = isRecoverable; this.errorCode = errorCode; this.format = format; } } /** Thrown when the sink encounters an unexpected timestamp discontinuity. */ final class UnexpectedDiscontinuityException extends Exception { /** The actual presentation time of a sample, in microseconds. */ public final long actualPresentationTimeUs; /** The expected presentation time of a sample, in microseconds. */ public final long expectedPresentationTimeUs; /** * Creates an instance. * * @param actualPresentationTimeUs The actual presentation time of a sample, in microseconds. * @param expectedPresentationTimeUs The expected presentation time of a sample, in * microseconds. */ public UnexpectedDiscontinuityException( long actualPresentationTimeUs, long expectedPresentationTimeUs) { super( ""Unexpected audio track timestamp discontinuity: expected "" + expectedPresentationTimeUs + "", got "" + actualPresentationTimeUs); this.actualPresentationTimeUs = actualPresentationTimeUs; this.expectedPresentationTimeUs = expectedPresentationTimeUs; } } /** * The level of support the sink provides for a format. One of {@link * #SINK_FORMAT_SUPPORTED_DIRECTLY}, {@link #SINK_FORMAT_SUPPORTED_WITH_TRANSCODING} or {@link * #SINK_FORMAT_UNSUPPORTED}. */ @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({ SINK_FORMAT_SUPPORTED_DIRECTLY, SINK_FORMAT_SUPPORTED_WITH_TRANSCODING, SINK_FORMAT_UNSUPPORTED }) @interface SinkFormatSupport {} /** The sink supports the format directly, without the need for internal transcoding. */ int SINK_FORMAT_SUPPORTED_DIRECTLY = 2; /** * The sink supports the format, but needs to transcode it internally to do so. Internal * transcoding may result in lower quality and higher CPU load in some cases. */ int SINK_FORMAT_SUPPORTED_WITH_TRANSCODING = 1; /** The sink does not support the format. */ int SINK_FORMAT_UNSUPPORTED = 0; /** Returned by {@link #getCurrentPositionUs(boolean)} when the position is not set. */ long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE; /** * Sets the listener for sink events, which should be the audio renderer. * * @param listener The listener for sink events, which should be the audio renderer. */ void setListener(Listener listener); /** * Sets the {@link PlayerId} of the player using this audio sink. * * @param playerId The {@link PlayerId}, or null to clear a previously set id. */ default void setPlayerId(@Nullable PlayerId playerId) {} /** * Returns whether the sink supports a given {@link Format}. * * @param format The format. * @return Whether the sink supports the format. */ boolean supportsFormat(Format format); /** * Returns the level of support that the sink provides for a given {@link Format}. * * @param format The format. * @return The level of support provided. */ @SinkFormatSupport int getFormatSupport(Format format); /** * Returns the playback position in the stream starting at zero, in microseconds, or {@link * #CURRENT_POSITION_NOT_SET} if it is not yet available. * * @param sourceEnded Specify {@code true} if no more input buffers will be provided. * @return The playback position relative to the start of playback, in microseconds. */ long getCurrentPositionUs(boolean sourceEnded); /** * Configures (or reconfigures) the sink. * * @param inputFormat The format of audio data provided in the input buffers. * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a * suitable buffer size. * @param outputChannels A mapping from input to output channels that is applied to this sink's * input as a preprocessing step, if handling PCM input. Specify {@code null} to leave the * input unchanged. Otherwise, the element at index {@code i} specifies index of the input * channel to map to output channel {@code i} when preprocessing input buffers. After the map * is applied the audio data will have {@code outputChannels.length} channels. * @throws ConfigurationException If an error occurs configuring the sink. */ void configure(Format inputFormat, int specifiedBufferSize, @Nullable int[] outputChannels) throws ConfigurationException; /** Starts or resumes consuming audio if initialized. */ void play(); /** Signals to the sink that the next buffer may be discontinuous with the previous buffer. */ void handleDiscontinuity(); /** * Attempts to process data from a {@link ByteBuffer}, starting from its current position and * ending at its limit (exclusive). The position of the {@link ByteBuffer} is advanced by the * number of bytes that were handled. {@link Listener#onPositionDiscontinuity()} will be called if * {@code presentationTimeUs} is discontinuous with the last buffer handled since the last reset. * *

    Returns whether the data was handled in full. If the data was not handled in full then the * same {@link ByteBuffer} must be provided to subsequent calls until it has been fully consumed, * except in the case of an intervening call to {@link #flush()} (or to {@link #configure(Format, * int, int[])} that causes the sink to be flushed). * * @param buffer The buffer containing audio data. * @param presentationTimeUs The presentation timestamp of the buffer in microseconds. * @param encodedAccessUnitCount The number of encoded access units in the buffer, or 1 if the * buffer contains PCM audio. This allows batching multiple encoded access units in one * buffer. * @return Whether the buffer was handled fully. * @throws InitializationException If an error occurs initializing the sink. * @throws WriteException If an error occurs writing the audio data. */ boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount) throws InitializationException, WriteException; /** * Processes any remaining data. {@link #isEnded()} will return {@code true} when no data remains. * * @throws WriteException If an error occurs draining data to the sink. */ void playToEndOfStream() throws WriteException; /** * Returns whether {@link #playToEndOfStream} has been called and all buffers have been processed. */ boolean isEnded(); /** Returns whether the sink has data pending that has not been consumed yet. */ boolean hasPendingData(); /** * Attempts to set the playback parameters. The audio sink may override these parameters if they * are not supported. * * @param playbackParameters The new playback parameters to attempt to set. */ void setPlaybackParameters(PlaybackParameters playbackParameters); /** Returns the active {@link PlaybackParameters}. */ PlaybackParameters getPlaybackParameters(); /** Sets whether silences should be skipped in the audio stream. */ void setSkipSilenceEnabled(boolean [MASK] ); /** Returns whether silences are skipped in the audio stream. */ boolean getSkipSilenceEnabled(); /** * Sets attributes for audio playback. If the attributes have changed and if the sink is not * configured for use with tunneling, then it is reset and the audio session id is cleared. * *

    If the sink is configured for use with tunneling then the audio attributes are ignored. The * sink is not reset and the audio session id is not cleared. The passed attributes will be used * if the sink is later re-configured into non-tunneled mode. * * @param audioAttributes The attributes for audio playback. */ void setAudioAttributes(AudioAttributes audioAttributes); /** * Returns the audio attributes used for audio playback, or {@code null} if the sink does not use * audio attributes. */ @Nullable AudioAttributes getAudioAttributes(); /** Sets the audio session id. */ void setAudioSessionId(int audioSessionId); /** Sets the auxiliary effect. */ void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); /** * Sets the preferred audio device. * * @param audioDeviceInfo The preferred {@linkplain AudioDeviceInfo audio device}, or null to * restore the default. */ @RequiresApi(23) default void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {} /** * Sets the offset that is added to the media timestamp before it is passed as {@code * presentationTimeUs} in {@link #handleBuffer(ByteBuffer, long, int)}. * * @param outputStreamOffsetUs The output stream offset in microseconds. */ default void setOutputStreamOffsetUs(long outputStreamOffsetUs) {} /** * Enables tunneling, if possible. The sink is reset if tunneling was previously disabled. * Enabling tunneling is only possible if the sink is based on a platform {@link AudioTrack}, and * requires platform API version 21 onwards. * * @throws IllegalStateException Thrown if enabling tunneling on platform API version < 21. */ void enableTunnelingV21(); /** * Disables tunneling. If tunneling was previously enabled then the sink is reset and any audio * session id is cleared. */ void disableTunneling(); /** * Sets the playback volume. * * @param volume Linear output gain to apply to all channels. Should be in the range [0.0, 1.0]. */ void setVolume(float volume); /** Pauses playback. */ void pause(); /** * Flushes the sink, after which it is ready to receive buffers from a new playback position. * *

    The audio session may remain active until {@link #reset()} is called. */ void flush(); /** * Flushes the sink, after which it is ready to receive buffers from a new playback position. * *

    Does not release the {@link AudioTrack} held by the sink. * *

    This method is experimental, and will be renamed or removed in a future release. * *

    Only for experimental use as part of {@link * MediaCodecAudioRenderer#experimentalSetEnableKeepAudioTrackOnSeek(boolean)}. */ void experimentalFlushWithoutAudioTrackRelease(); /** Resets the sink, releasing any resources that it currently holds. */ void reset(); /** Releases the audio sink. */ default void release() {} } ","skipSilenceEnabled " "/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.http.multipart; import io.netty.buffer.ByteBuf; import io.netty.buffer.CompositeByteBuf; import io.netty.handler.codec.http.HttpConstants; import io.netty.util.internal.ObjectUtil; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import static io.netty.buffer.Unpooled.EMPTY_BUFFER; import static io.netty.buffer.Unpooled.buffer; import static io.netty.buffer.Unpooled.compositeBuffer; import static io.netty.buffer.Unpooled.wrappedBuffer; /** * Abstract Memory HttpData implementation */ public abstract class AbstractMemoryHttpData extends AbstractHttpData { private ByteBuf byteBuf; private int chunkPosition; protected AbstractMemoryHttpData(String name, Charset charset, long size) { super(name, charset, size); byteBuf = EMPTY_BUFFER; } @Override public void setContent(ByteBuf buffer) throws IOException { ObjectUtil.checkNotNull(buffer, ""buffer""); long localsize = buffer.readableBytes(); try { checkSize(localsize); } catch (IOException e) { buffer.release(); throw e; } if (definedSize > 0 && definedSize < localsize) { buffer.release(); throw new IOException(""Out of size: "" + localsize + "" > "" + definedSize); } if (byteBuf != null) { byteBuf.release(); } byteBuf = buffer; size = localsize; setCompleted(); } @Override public void setContent(InputStream inputStream) throws IOException { ObjectUtil.checkNotNull(inputStream, ""inputStream""); byte[] bytes = new byte[4096 * 4]; ByteBuf buffer = buffer(); int written = 0; try { int read = inputStream.read(bytes); while (read > 0) { buffer.writeBytes(bytes, 0, read); written += read; checkSize(written); read = inputStream.read(bytes); } } catch (IOException e) { buffer.release(); throw e; } size = written; if (definedSize > 0 && definedSize < size) { buffer.release(); throw new IOException(""Out of size: "" + size + "" > "" + definedSize); } if (byteBuf != null) { byteBuf.release(); } byteBuf = buffer; setCompleted(); } @Override public void addContent(ByteBuf buffer, boolean last) throws IOException { if (buffer != null) { long localsize = buffer.readableBytes(); try { checkSize(size + localsize); } catch (IOException e) { buffer.release(); throw e; } if (definedSize > 0 && definedSize < size + localsize) { buffer.release(); throw new IOException(""Out of size: "" + (size + localsize) + "" > "" + definedSize); } size += localsize; if (byteBuf == null) { byteBuf = buffer; } else if (localsize == 0) { // Nothing to add and byteBuf already exists buffer.release(); } else if (byteBuf.readableBytes() == 0) { // Previous buffer is empty, so just replace it byteBuf.release(); byteBuf = buffer; } else if (byteBuf instanceof CompositeByteBuf) { CompositeByteBuf cbb = (CompositeByteBuf) byteBuf; cbb.addComponent(true, buffer); } else { CompositeByteBuf cbb = compositeBuffer(Integer.MAX_VALUE); cbb.addComponents(true, byteBuf, buffer); byteBuf = cbb; } } if (last) { setCompleted(); } else { ObjectUtil.checkNotNull(buffer, ""buffer""); } } @Override public void setContent(File file) throws IOException { ObjectUtil.checkNotNull(file, ""file""); long newsize = file.length(); if (newsize > Integer.MAX_VALUE) { throw new IllegalArgumentException(""File too big to be loaded in memory""); } checkSize(newsize); RandomAccessFile [MASK] = new RandomAccessFile(file, ""r""); ByteBuffer byteBuffer; try { FileChannel fileChannel = [MASK] .getChannel(); try { byte[] array = new byte[(int) newsize]; byteBuffer = ByteBuffer.wrap(array); int read = 0; while (read < newsize) { read += fileChannel.read(byteBuffer); } } finally { fileChannel.close(); } } finally { [MASK] .close(); } byteBuffer.flip(); if (byteBuf != null) { byteBuf.release(); } byteBuf = wrappedBuffer(Integer.MAX_VALUE, byteBuffer); size = newsize; setCompleted(); } @Override public void delete() { if (byteBuf != null) { byteBuf.release(); byteBuf = null; } } @Override public byte[] get() { if (byteBuf == null) { return EMPTY_BUFFER.array(); } byte[] array = new byte[byteBuf.readableBytes()]; byteBuf.getBytes(byteBuf.readerIndex(), array); return array; } @Override public String getString() { return getString(HttpConstants.DEFAULT_CHARSET); } @Override public String getString(Charset encoding) { if (byteBuf == null) { return """"; } if (encoding == null) { encoding = HttpConstants.DEFAULT_CHARSET; } return byteBuf.toString(encoding); } /** * Utility to go from a In Memory FileUpload * to a Disk (or another implementation) FileUpload * @return the attached ByteBuf containing the actual bytes */ @Override public ByteBuf getByteBuf() { return byteBuf; } @Override public ByteBuf getChunk(int length) throws IOException { if (byteBuf == null || length == 0 || byteBuf.readableBytes() == 0) { chunkPosition = 0; return EMPTY_BUFFER; } int sizeLeft = byteBuf.readableBytes() - chunkPosition; if (sizeLeft == 0) { chunkPosition = 0; return EMPTY_BUFFER; } int sliceLength = length; if (sizeLeft < length) { sliceLength = sizeLeft; } ByteBuf chunk = byteBuf.retainedSlice(chunkPosition, sliceLength); chunkPosition += sliceLength; return chunk; } @Override public boolean isInMemory() { return true; } @Override public boolean renameTo(File dest) throws IOException { ObjectUtil.checkNotNull(dest, ""dest""); if (byteBuf == null) { // empty file if (!dest.createNewFile()) { throw new IOException(""file exists already: "" + dest); } return true; } int length = byteBuf.readableBytes(); long written = 0; RandomAccessFile [MASK] = new RandomAccessFile(dest, ""rw""); try { FileChannel fileChannel = [MASK] .getChannel(); try { if (byteBuf.nioBufferCount() == 1) { ByteBuffer byteBuffer = byteBuf.nioBuffer(); while (written < length) { written += fileChannel.write(byteBuffer); } } else { ByteBuffer[] byteBuffers = byteBuf.nioBuffers(); while (written < length) { written += fileChannel.write(byteBuffers); } } fileChannel.force(false); } finally { fileChannel.close(); } } finally { [MASK] .close(); } return written == length; } @Override public File getFile() throws IOException { throw new IOException(""Not represented by a file""); } @Override public HttpData touch() { return touch(null); } @Override public HttpData touch(Object hint) { if (byteBuf != null) { byteBuf.touch(hint); } return this; } } ","accessFile " "/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.compression; import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; import io.netty. [MASK] .ByteBuf; import io.netty. [MASK] .ByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; /** * Decompresses a {@link ByteBuf} using the deflate algorithm. */ public abstract class ZlibDecoder extends ByteToMessageDecoder { /** * Maximum allowed size of the decompression [MASK] . */ protected final int maxAllocation; /** * Same as {@link #ZlibDecoder(int)} with maxAllocation = 0. */ public ZlibDecoder() { this(0); } /** * Construct a new ZlibDecoder. * @param maxAllocation * Maximum size of the decompression [MASK] . Must be >= 0. * If zero, maximum size is decided by the {@link ByteBufAllocator}. */ public ZlibDecoder(int maxAllocation) { this.maxAllocation = checkPositiveOrZero(maxAllocation, ""maxAllocation""); } /** * Returns {@code true} if and only if the end of the compressed stream * has been reached. */ public abstract boolean isClosed(); /** * Allocate or expand the decompression [MASK] , without exceeding the maximum allocation. * Calls {@link #decompressionBufferExhausted(ByteBuf)} if the [MASK] is full and cannot be expanded further. */ protected ByteBuf prepareDecompressBuffer(ChannelHandlerContext ctx, ByteBuf [MASK] , int preferredSize) { if ( [MASK] == null) { if (maxAllocation == 0) { return ctx.alloc().heapBuffer(preferredSize); } return ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation); } // this always expands the [MASK] if possible, even if the expansion is less than preferredSize // we throw the exception only if the [MASK] could not be expanded at all // this means that one final attempt to deserialize will always be made with the [MASK] at maxAllocation if ( [MASK] .ensureWritable(preferredSize, true) == 1) { // [MASK] must be consumed so subclasses don't add it to output // we therefore duplicate it when calling decompressionBufferExhausted() to guarantee non-interference // but wait until after to consume it so the subclass can tell how much output is really in the [MASK] decompressionBufferExhausted( [MASK] .duplicate()); [MASK] .skipBytes( [MASK] .readableBytes()); throw new DecompressionException(""Decompression [MASK] has reached maximum size: "" + [MASK] .maxCapacity()); } return [MASK] ; } /** * Called when the decompression [MASK] cannot be expanded further. * Default implementation is a no-op, but subclasses can override in case they want to * do something before the {@link DecompressionException} is thrown, such as log the * data that was decompressed so far. */ protected void decompressionBufferExhausted(ByteBuf [MASK] ) { } } ","buffer " "// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.java; import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static com.google.devtools.build.lib.rules.java.JavaStarlarkCommon.checkPrivateAccess; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.PackageSpecificationProvider; import com.google.devtools.build.lib.analysis.ProviderCollection; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.RuleErrorConsumer; import com.google.devtools.build.lib.analysis.platform.ToolchainInfo; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.Depset; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.packages.BuiltinProvider; import com.google.devtools.build.lib.packages.NativeInfo; import com.google.devtools.build.lib.packages.PackageSpecification.PackageGroupContents; import com.google.devtools.build.lib.packages.Provider; import com.google.devtools.build.lib.rules.java.JavaPluginInfo.JavaPluginData; import com.google.devtools.build.lib.starlarkbuildapi.java.JavaToolchainStarlarkApiProviderApi; import java.util.Iterator; import javax.annotation.Nullable; import net.starlark.java.eval.EvalException; import net.starlark.java.eval.StarlarkList; import net.starlark.java.eval.StarlarkThread; /** Information about the JDK used by the java_* rules. */ @Immutable public final class JavaToolchainProvider extends NativeInfo implements JavaToolchainStarlarkApiProviderApi { public static final BuiltinProvider PROVIDER = new BuiltinProvider( ""JavaToolchainInfo"", JavaToolchainProvider.class) {}; /** Returns the Java Toolchain associated with the rule being analyzed or {@code null}. */ public static JavaToolchainProvider from(RuleContext ruleContext) { ToolchainInfo toolchainInfo = ruleContext.getToolchainInfo( ruleContext .getPrerequisite(JavaRuleClasses.JAVA_TOOLCHAIN_TYPE_ATTRIBUTE_NAME) .getLabel()); return from(toolchainInfo, ruleContext); } @VisibleForTesting public static JavaToolchainProvider from(ProviderCollection collection) { ToolchainInfo toolchainInfo = collection.get(ToolchainInfo.PROVIDER); return from(toolchainInfo, null); } @Nullable private static JavaToolchainProvider from( ToolchainInfo toolchainInfo, @Nullable RuleErrorConsumer errorConsumer) { if (toolchainInfo != null) { try { JavaToolchainProvider provider = (JavaToolchainProvider) toolchainInfo.getValue(""java""); if (provider != null) { return provider; } } catch (EvalException e) { if (errorConsumer != null) { errorConsumer.ruleError( String.format(""There was an error reading the Java toolchain: %s"", e)); } } } if (errorConsumer != null) { errorConsumer.ruleError(""The selected Java toolchain is not a JavaToolchainProvider""); } return null; } public static JavaToolchainProvider create( Label label, ImmutableList javacOptions, NestedSet [MASK] , boolean javacSupportsWorkers, boolean javacSupportsMultiplexWorkers, boolean javacSupportsWorkerCancellation, BootClassPathInfo bootclasspath, NestedSet tools, JavaToolchainTool javaBuilder, @Nullable JavaToolchainTool headerCompiler, @Nullable JavaToolchainTool headerCompilerDirect, @Nullable AndroidLintTool androidLint, JspecifyInfo jspecifyInfo, @Nullable JavaToolchainTool bytecodeOptimizer, ImmutableList localJavaOptimizationConfiguration, ImmutableSet headerCompilerBuiltinProcessors, ImmutableSet reducedClasspathIncompatibleProcessors, boolean forciblyDisableHeaderCompilation, FilesToRunProvider singleJar, @Nullable FilesToRunProvider oneVersion, @Nullable Artifact oneVersionAllowlist, @Nullable Artifact oneVersionAllowlistForTests, Artifact genClass, @Nullable Artifact depsChecker, @Nullable Artifact timezoneData, FilesToRunProvider ijar, ImmutableListMultimap compatibleJavacOptions, ImmutableList packageConfiguration, FilesToRunProvider jacocoRunner, FilesToRunProvider proguardAllowlister, JavaSemantics javaSemantics, JavaRuntimeInfo javaRuntime) { return new JavaToolchainProvider( label, bootclasspath, tools, javaBuilder, headerCompiler, headerCompilerDirect, androidLint, jspecifyInfo, bytecodeOptimizer, localJavaOptimizationConfiguration, headerCompilerBuiltinProcessors, reducedClasspathIncompatibleProcessors, forciblyDisableHeaderCompilation, singleJar, oneVersion, oneVersionAllowlist, oneVersionAllowlistForTests, genClass, depsChecker, timezoneData, ijar, compatibleJavacOptions, javacOptions, [MASK] , javacSupportsWorkers, javacSupportsMultiplexWorkers, javacSupportsWorkerCancellation, packageConfiguration, jacocoRunner, proguardAllowlister, javaSemantics, javaRuntime); } private final Label label; private final BootClassPathInfo bootclasspath; private final NestedSet tools; private final JavaToolchainTool javaBuilder; @Nullable private final JavaToolchainTool headerCompiler; @Nullable private final JavaToolchainTool headerCompilerDirect; @Nullable private final AndroidLintTool androidLint; @Nullable private final JspecifyInfo jspecifyInfo; @Nullable private final JavaToolchainTool bytecodeOptimizer; private final ImmutableList localJavaOptimizationConfiguration; private final ImmutableSet headerCompilerBuiltinProcessors; private final ImmutableSet reducedClasspathIncompatibleProcessors; private final boolean forciblyDisableHeaderCompilation; private final FilesToRunProvider singleJar; @Nullable private final FilesToRunProvider oneVersion; @Nullable private final Artifact oneVersionAllowlist; @Nullable private final Artifact oneVersionAllowlistForTests; private final Artifact genClass; @Nullable private final Artifact depsChecker; @Nullable private final Artifact timezoneData; private final FilesToRunProvider ijar; private final ImmutableMap> compatibleJavacOptions; private final NestedSet javacOptions; private final String sourceVersion; private final String targetVersion; private final NestedSet [MASK] ; private final boolean javacSupportsWorkers; private final boolean javacSupportsMultiplexWorkers; private final boolean javacSupportsWorkerCancellation; private final ImmutableList packageConfiguration; private final FilesToRunProvider jacocoRunner; private final FilesToRunProvider proguardAllowlister; private final JavaSemantics javaSemantics; private final JavaRuntimeInfo javaRuntime; private JavaToolchainProvider( Label label, BootClassPathInfo bootclasspath, NestedSet tools, JavaToolchainTool javaBuilder, @Nullable JavaToolchainTool headerCompiler, @Nullable JavaToolchainTool headerCompilerDirect, @Nullable AndroidLintTool androidLint, @Nullable JspecifyInfo jspecifyInfo, @Nullable JavaToolchainTool bytecodeOptimizer, ImmutableList localJavaOptimizationConfiguration, ImmutableSet headerCompilerBuiltinProcessors, ImmutableSet reducedClasspathIncompatibleProcessors, boolean forciblyDisableHeaderCompilation, FilesToRunProvider singleJar, @Nullable FilesToRunProvider oneVersion, @Nullable Artifact oneVersionAllowlist, @Nullable Artifact oneVersionAllowlistForTests, Artifact genClass, @Nullable Artifact depsChecker, @Nullable Artifact timezoneData, FilesToRunProvider ijar, ImmutableListMultimap compatibleJavacOptions, ImmutableList javacOptions, NestedSet [MASK] , boolean javacSupportsWorkers, boolean javacSupportsMultiplexWorkers, boolean javacSupportsWorkerCancellation, ImmutableList packageConfiguration, FilesToRunProvider jacocoRunner, FilesToRunProvider proguardAllowlister, JavaSemantics javaSemantics, JavaRuntimeInfo javaRuntime) { this.label = label; this.bootclasspath = bootclasspath; this.tools = tools; this.javaBuilder = javaBuilder; this.headerCompiler = headerCompiler; this.headerCompilerDirect = headerCompilerDirect; this.androidLint = androidLint; this.jspecifyInfo = jspecifyInfo; this.bytecodeOptimizer = bytecodeOptimizer; this.localJavaOptimizationConfiguration = localJavaOptimizationConfiguration; this.headerCompilerBuiltinProcessors = headerCompilerBuiltinProcessors; this.reducedClasspathIncompatibleProcessors = reducedClasspathIncompatibleProcessors; this.forciblyDisableHeaderCompilation = forciblyDisableHeaderCompilation; this.singleJar = singleJar; this.oneVersion = oneVersion; this.oneVersionAllowlist = oneVersionAllowlist; this.oneVersionAllowlistForTests = oneVersionAllowlistForTests; this.genClass = genClass; this.depsChecker = depsChecker; this.timezoneData = timezoneData; this.ijar = ijar; this.compatibleJavacOptions = ImmutableMap.copyOf( Maps.transformValues( compatibleJavacOptions.asMap(), JavaHelper::detokenizeJavaOptions)); this.javacOptions = JavaHelper.detokenizeJavaOptions(javacOptions); this.sourceVersion = findSourceVersion(javacOptions); this.targetVersion = findTargetVersion(javacOptions); this. [MASK] = [MASK] ; this.javacSupportsWorkers = javacSupportsWorkers; this.javacSupportsMultiplexWorkers = javacSupportsMultiplexWorkers; this.javacSupportsWorkerCancellation = javacSupportsWorkerCancellation; this.packageConfiguration = packageConfiguration; this.jacocoRunner = jacocoRunner; this.proguardAllowlister = proguardAllowlister; this.javaSemantics = javaSemantics; this.javaRuntime = javaRuntime; } /** Returns the label for this {@code java_toolchain}. */ @Override public Label getToolchainLabel() { return label; } /** Returns the target Java bootclasspath. */ public BootClassPathInfo getBootclasspath() { return bootclasspath; } /** Returns the {@link Artifact}s of compilation tools. */ public NestedSet getTools() { return tools; } /** Returns the {@link JavaToolchainTool} for JavaBuilder */ public JavaToolchainTool getJavaBuilder() { return javaBuilder; } /** Returns the {@link JavaToolchainTool} for the header compiler */ @Nullable public JavaToolchainTool getHeaderCompiler() { return headerCompiler; } /** * Returns the {@link FilesToRunProvider} of the Header Compiler deploy jar for direct-classpath, * non-annotation processing actions. */ @Nullable public JavaToolchainTool getHeaderCompilerDirect() { return headerCompilerDirect; } @Nullable public AndroidLintTool getAndroidLint() { return androidLint; } @Nullable public JspecifyInfo jspecifyInfo() { return jspecifyInfo; } @Nullable public JavaToolchainTool getBytecodeOptimizer() { return bytecodeOptimizer; } public ImmutableList getLocalJavaOptimizationConfiguration() { return localJavaOptimizationConfiguration; } /** Returns class names of annotation processors that are built in to the header compiler. */ public ImmutableSet getHeaderCompilerBuiltinProcessors() { return headerCompilerBuiltinProcessors; } public ImmutableSet getReducedClasspathIncompatibleProcessors() { return reducedClasspathIncompatibleProcessors; } /** * Returns {@code true} if header compilation should be forcibly disabled, overriding * --java_header_compilation. */ public boolean getForciblyDisableHeaderCompilation() { return forciblyDisableHeaderCompilation; } @Override public boolean getForciblyDisableHeaderCompilationStarlark(StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return getForciblyDisableHeaderCompilation(); } @Override public boolean hasHeaderCompiler(StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return getHeaderCompiler() != null; } @Override public boolean hasHeaderCompilerDirect(StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return getHeaderCompilerDirect() != null; } /** Returns the {@link FilesToRunProvider} of the SingleJar tool. */ @Override public FilesToRunProvider getSingleJar() { return singleJar; } /** * Return the {@link FilesToRunProvider} of the tool that enforces one-version compliance of Java * binaries. */ @Override @Nullable public FilesToRunProvider getOneVersionBinary() { return oneVersion; } /** Return the {@link Artifact} of the allowlist used by the one-version compliance checker. */ @Nullable @Override public Artifact getOneVersionAllowlist() { return oneVersionAllowlist; } /** * Return the {@link Artifact} of the one-version allowlist for tests used by the one-version * compliance checker. */ @Nullable public Artifact oneVersionAllowlistForTests() { return oneVersionAllowlistForTests; } @Override @Nullable public Artifact getOneVersionAllowlistForTests(StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return oneVersionAllowlistForTests(); } /** Returns the {@link Artifact} of the GenClass deploy jar */ public Artifact getGenClass() { return genClass; } /** Returns the {@link Artifact} of the ImportDepsChecker deploy jar */ @Nullable public Artifact depsChecker() { return depsChecker; } @Override public Artifact getDepsCheckerForStarlark(StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return depsChecker(); } /** * Returns the {@link Artifact} of the latest timezone data resource jar that can be loaded by * Java 8 binaries. */ @Nullable public Artifact getTimezoneData() { return timezoneData; } /** Returns the ijar executable */ @Override public FilesToRunProvider getIjar() { return ijar; } /** Returns the map of target environment-specific javacopts. */ public NestedSet getCompatibleJavacOptions(String key) { return compatibleJavacOptions.getOrDefault( key, NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER)); } public ImmutableList getCompatibleJavacOptionsAsList(String key) { return JavaHelper.tokenizeJavaOptions(getCompatibleJavacOptions(key)); } /** Returns the list of default options for the java compiler. */ public NestedSet getJavacOptions(RuleContext ruleContext) { NestedSetBuilder result = NestedSetBuilder.naiveLinkOrder(); result.addTransitive(javacOptions); if (ruleContext != null) { // TODO(b/78512644): require ruleContext to be non-null after java_common.default_javac_opts // is turned down result.addTransitive(ruleContext.getFragment(JavaConfiguration.class).getDefaultJavacFlags()); } return result.build(); } public ImmutableList getJavacOptionsAsList(RuleContext ruleContext) { ImmutableList.Builder result = ImmutableList.builder().addAll(JavaHelper.tokenizeJavaOptions(javacOptions)); if (ruleContext != null) { // TODO(b/78512644): require ruleContext to be non-null after java_common.default_javac_opts // is turned down result.addAll( ruleContext.getFragment(JavaConfiguration.class).getDefaultJavacFlagsForStarlarkAsList()); } return result.build(); } /** * Returns the NestedSet of default options for the JVM running the java compiler and associated * tools. */ public NestedSet getJvmOptions() { return [MASK] ; } /** Returns whether JavaBuilders supports running as a persistent worker or not. */ public boolean getJavacSupportsWorkers() { return javacSupportsWorkers; } /** Returns whether JavaBuilders supports running persistent workers in multiplex mode */ public boolean getJavacSupportsMultiplexWorkers() { return javacSupportsMultiplexWorkers; } /** Returns whether JavaBuilders supports running persistent workers with cancellation */ public boolean getJavacSupportsWorkerCancellation() { return javacSupportsWorkerCancellation; } /** Returns the global {@code java_package_configuration} data. */ public ImmutableList packageConfiguration() { return packageConfiguration; } @Override public FilesToRunProvider getJacocoRunner() { return jacocoRunner; } @Override public FilesToRunProvider getProguardAllowlister() { return proguardAllowlister; } @Override public StarlarkList getPackageConfigurationStarlark( StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return StarlarkList.immutableCopyOf(packageConfiguration); } public JavaSemantics getJavaSemantics() { return javaSemantics; } @Override public JavaRuntimeInfo getJavaRuntime() { return javaRuntime; } @Override @Nullable public AndroidLintTool stalarkAndroidLinter(StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return getAndroidLint(); } /** Returns the input Java language level */ // TODO(cushon): remove this API; it bakes a deprecated detail of the javac API into Bazel @Override public String getSourceVersion() { return sourceVersion; } private static String findSourceVersion(ImmutableList javacOptions) { Iterator it = javacOptions.iterator(); while (it.hasNext()) { if (it.next().equals(""-source"") && it.hasNext()) { return it.next(); } } return JAVA_SPECIFICATION_VERSION.value(); } /** Returns the target Java language level */ // TODO(cushon): remove this API; it bakes a deprecated detail of the javac API into Bazel @Override public String getTargetVersion() { return targetVersion; } private static String findTargetVersion(ImmutableList javacOptions) { Iterator it = javacOptions.iterator(); while (it.hasNext()) { if (it.next().equals(""-target"") && it.hasNext()) { return it.next(); } } return JAVA_SPECIFICATION_VERSION.value(); } @Override public Depset getStarlarkBootclasspath() { return Depset.of(Artifact.class, getBootclasspath().bootclasspath()); } @Override public Depset getStarlarkJvmOptions() { return Depset.of(String.class, getJvmOptions()); } @Override public Depset getStarlarkTools() { return Depset.of(Artifact.class, getTools()); } @Override public Provider getProvider() { return PROVIDER; } @Nullable @Override public Artifact getTimezoneDataForStarlark(StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); return getTimezoneData(); } @Override public Object getCompatibleJavacOptionsForStarlark( String key, boolean asDepset, StarlarkThread thread) throws EvalException { checkPrivateAccess(thread); if (asDepset) { return Depset.of(String.class, getCompatibleJavacOptions(key)); } else { return getCompatibleJavacOptionsAsList(key); } } @AutoValue abstract static class JspecifyInfo { abstract JavaPluginData jspecifyProcessor(); abstract NestedSet jspecifyImplicitDeps(); abstract ImmutableList jspecifyJavacopts(); abstract ImmutableList jspecifyPackages(); boolean matches(Label label) { for (PackageSpecificationProvider provider : jspecifyPackages()) { for (PackageGroupContents specifications : provider.getPackageSpecifications().toList()) { if (specifications.containsPackage(label.getPackageIdentifier())) { return true; } } } return false; } static JspecifyInfo create( JavaPluginData jspecifyProcessor, NestedSet jspecifyImplicitDeps, ImmutableList jspecifyJavacopts, ImmutableList jspecifyPackages) { return new AutoValue_JavaToolchainProvider_JspecifyInfo( jspecifyProcessor, jspecifyImplicitDeps, jspecifyJavacopts, jspecifyPackages); } } } ","jvmOptions " "// Copyright 2021 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the ""License""); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an ""AS IS"" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.collect.nestedset; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.util.concurrent.Futures.immediateVoidFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.auto.value.AutoValue; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.devtools.build.lib.bugreport.BugReporter; import com.google.devtools.build.lib.collect.nestedset.NestedSetStore.FingerprintComputationResult; import com.google.devtools.build.lib.skyframe.serialization.SerializationConstants; import com.google.protobuf.ByteString; import javax.annotation.Nullable; /** * A bidirectional, in-memory, weak cache for fingerprint ⟺ {@link NestedSet} associations. * *

    For use by {@link NestedSetStore} to minimize work during {@link NestedSet} (de)serialization. * *

    The cache supports the possibility of semantically different arrays having the same serialized * representation. For this reason, a [MASK] object is included in the key for the fingerprint ⟹ * array mapping. This object should encapsulate all additional [MASK] necessary to deserialize a * {@link NestedSet} element. The array ⟹ fingerprint mapping, on the other hand, is expected to be * deterministic. */ class NestedSetSerializationCache { /** * Fingerprint to array cache. * *

    The values in this cache are always {@code Object[]} or {@code ListenableFuture}. * We avoid a common wrapper object both for memory efficiency and because our cache eviction * policy is based on value GC, and wrapper objects would defeat that. * *

    While a fetch for the contents is outstanding, the value in the cache will be a {@link * ListenableFuture}. When it is resolved, it is replaced with the unwrapped {@code Object[]}. * This is done because if the array is a transitive member, its future may be GC'd, and we want * entries to stay in this cache while the contents are still live. */ private final Cache fingerprintToContents = Caffeine.newBuilder() .initialCapacity(SerializationConstants.DESERIALIZATION_POOL_SIZE) .weakValues() .build(); /** {@code Object[]} contents to fingerprint. Maintained for fast fingerprinting. */ private final Cache contentsToFingerprint = Caffeine.newBuilder() .initialCapacity(SerializationConstants.DESERIALIZATION_POOL_SIZE) .weakKeys() .build(); private final BugReporter bugReporter; NestedSetSerializationCache(BugReporter bugReporter) { this.bugReporter = bugReporter; } /** * Returns contents (an {@code Object[]} or a {@code ListenableFuture}) for the {@link * NestedSet} associated with the given fingerprint if there was already one. Otherwise associates * {@code future} with {@code fingerprint} and returns {@code null}. * *

    Upon a {@code null} return, the caller should ensure that the given future is eventually set * with the fetched contents. * *

    Upon a non-{@code null} return, the caller should discard the given future in favor of the * returned contents, blocking for them if the return value is itself a future. * * @param fingerprint the fingerprint of the desired {@link NestedSet} contents * @param [MASK] the [MASK] needed to deterministically deserialize the contents associated with * {@code fingerprint} * @param future a freshly created {@link SettableFuture} */ @Nullable Object putFutureIfAbsent( ByteString fingerprint, SettableFuture future, Object [MASK] ) { checkArgument(!future.isDone(), ""Must pass a fresh future: %s"", future); Object existing = fingerprintToContents.asMap().putIfAbsent(FingerprintKey.of(fingerprint, [MASK] ), future); if (existing != null) { return existing; } // This is the first request of this fingerprint. unwrapWhenDone(fingerprint, future, [MASK] ); return null; } /** * Registers a {@link FutureCallback} that associates the provided fingerprint and the contents of * the future, when it completes. */ private void unwrapWhenDone( ByteString fingerprint, ListenableFuture futureContents, Object [MASK] ) { Futures.addCallback( futureContents, new FutureCallback() { @Override public void onSuccess(Object[] contents) { // Store a FingerprintComputationResult so that we can skip fingerprinting this array // and writing it to storage (it's already there - we just fetched it). Also replace the // cached future with the unwrapped contents, since the future may be GC'd. If there was // a call to putIfAbsent with this fingerprint while the future was pending, we may // overwrite a fingerprint ⟹ array mapping, but this is fine since both arrays have // the same contents. In this case, it would be nice to also complete the other array's // write future, but the semantics of SettableFuture makes this difficult (set after // setFuture has no effect). putIfAbsent( contents, FingerprintComputationResult.create(fingerprint, immediateVoidFuture()), [MASK] ); } @Override public void onFailure(Throwable t) { // Failure to fetch the NestedSet contents is unexpected, but the failed future can be // stored as the NestedSet children. This way the exception is only propagated if the // NestedSet is consumed (unrolled). bugReporter.sendNonFatalBugReport(t); } }, directExecutor()); } /** * Retrieves the fingerprint associated with the given {@link NestedSet} contents, or {@code null} * if the given contents are not known. */ @Nullable FingerprintComputationResult fingerprintForContents(Object[] contents) { return contentsToFingerprint.getIfPresent(contents); } /** * Ensures that a fingerprint ⟺ contents association is cached in both directions. * *

    If the given fingerprint and array are already fully cached, returns the existing * {@link FingerprintComputationResult}. Otherwise returns {@code null}. * *

    If the given fingerprint is only partially cached (meaning that {@link * #putFutureIfAbsent} has been called but the associated future has not yet completed), then the * cached future is overwritten in favor of the actual contents. */ @Nullable FingerprintComputationResult putIfAbsent( Object[] contents, FingerprintComputationResult result, Object [MASK] ) { FingerprintComputationResult existingResult = contentsToFingerprint.asMap().putIfAbsent(contents, result); if (existingResult != null) { return existingResult; } fingerprintToContents.put(FingerprintKey.of(result.fingerprint(), [MASK] ), contents); return null; } @AutoValue abstract static class FingerprintKey { abstract ByteString fingerprint(); abstract Object [MASK] (); static FingerprintKey of(ByteString fingerprint, Object [MASK] ) { return new AutoValue_NestedSetSerializationCache_FingerprintKey(fingerprint, [MASK] ); } } } ","context " "/* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.muxer; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import android.content.Context; import android.media.MediaCodec; import android.media.MediaExtractor; import androidx.test.core.app.ApplicationProvider; import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.testutil.DumpFileAsserts; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.MediaFormatUtil; import com.google.common.collect.ImmutableList; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; /** End to end instrumentation tests for {@link Mp4Muxer}. */ @RunWith(Parameterized.class) public class Mp4MuxerEndToEndTest { private static final String H264_MP4 = ""sample.mp4""; private static final String H265_HDR10_MP4 = ""hdr10-720p.mp4""; private static final String H265_WITH_METADATA_TRACK_MP4 = ""h265_with_metadata_track.mp4""; private static final String AV1_MP4 = ""sample_av1.mp4""; @Parameters(name = ""{0}"") public static ImmutableList mediaSamples() { return ImmutableList.of(H264_MP4, H265_HDR10_MP4, H265_WITH_METADATA_TRACK_MP4, AV1_MP4); } @Parameter public @MonotonicNonNull String inputFile; @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); private static final String MP4_FILE_ASSET_DIRECTORY = ""media/mp4/""; private final Context context = ApplicationProvider.getApplicationContext(); private @MonotonicNonNull String outputPath; private @MonotonicNonNull FileOutputStream outputStream; @Before public void setUp() throws Exception { outputPath = temporaryFolder.newFile(""muxeroutput.mp4"").getPath(); outputStream = new FileOutputStream(outputPath); } @After public void tearDown() throws IOException { checkNotNull(outputStream).close(); } @Test public void createMp4File_fromInputFileSampleData_matchesExpected() throws IOException { Mp4Muxer mp4Muxer = null; try { mp4Muxer = new Mp4Muxer.Builder(checkNotNull(outputStream)).build(); mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L); feedInputDataToMuxer(mp4Muxer, checkNotNull(inputFile)); } finally { if (mp4Muxer != null) { mp4Muxer.close(); } } FakeExtractorOutput fakeExtractorOutput = TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), checkNotNull(outputPath)); DumpFileAsserts.assertOutput( context, fakeExtractorOutput, AndroidMuxerTestUtil.getExpectedDumpFilePath(inputFile)); } @Test public void createMp4File_muxerNotClosed_createsPartiallyWrittenValidFile() throws IOException { Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(checkNotNull(outputStream)).build(); mp4Muxer.setModificationTime(/* timestampMs= */ 500_000_000L); feedInputDataToMuxer(mp4Muxer, H265_HDR10_MP4); // Muxer not closed. // Audio sample written = 192 out of 195. // Video sample written = 94 out of 127. // Output is still a valid MP4 file. FakeExtractorOutput fakeExtractorOutput = TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), checkNotNull(outputPath)); DumpFileAsserts.assertOutput( context, fakeExtractorOutput, AndroidMuxerTestUtil.getExpectedDumpFilePath(""partial_"" + H265_HDR10_MP4)); } private void feedInputDataToMuxer(Mp4Muxer mp4Muxer, String inputFileName) throws IOException { MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource( context.getResources().getAssets().openFd(MP4_FILE_ASSET_DIRECTORY + inputFileName)); List addedTracks = new ArrayList<>(); int sortKey = 0; for (int i = 0; i < extractor.getTrackCount(); i++) { Mp4Muxer.TrackToken trackToken = mp4Muxer.addTrack( sortKey++, MediaFormatUtil.createFormatFromMediaFormat(extractor.getTrackFormat(i))); addedTracks.add(trackToken); extractor.selectTrack(i); } do { MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); bufferInfo.flags = extractor.getSampleFlags(); bufferInfo.offset = 0; bufferInfo.presentationTimeUs = extractor.getSampleTime(); int [MASK] = (int) extractor.getSampleSize(); bufferInfo.size = [MASK] ; ByteBuffer sampleBuffer = ByteBuffer.allocateDirect( [MASK] ); extractor.readSampleData(sampleBuffer, /* offset= */ 0); sampleBuffer.rewind(); mp4Muxer.writeSampleData( addedTracks.get(extractor.getSampleTrackIndex()), sampleBuffer, bufferInfo); } while (extractor.advance()); extractor.release(); } } ","sampleSize " "/* * Copyright 2015 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the ""License""); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.http; import io.netty.handler.codec.http.HttpHeadersTestUtils.HeaderValue; import io.netty.util.AsciiString; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.StringUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; import static io.netty.handler.codec.http.HttpHeaderValues.ZERO; import static io.netty.handler.codec.http.HttpHeadersTestUtils.of; import static io.netty.util.AsciiString.contentEquals; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class DefaultHttpHeadersTest { private static final CharSequence HEADER_NAME = ""testHeader""; private static final CharSequence ILLEGAL_VALUE = ""testHeader\r\nContent-Length:45\r\n\r\n""; @Test public void nullHeaderNameNotAllowed() { assertThrows(IllegalArgumentException.class, new Executable() { @Override public void execute() { new DefaultHttpHeaders().add(null, ""foo""); } }); } @Test public void emptyHeaderNameNotAllowed() { assertThrows(IllegalArgumentException.class, new Executable() { @Override public void execute() { new DefaultHttpHeaders().add(StringUtil.EMPTY_STRING, ""foo""); } }); } @Test public void keysShouldBeCaseInsensitive() { DefaultHttpHeaders headers = new DefaultHttpHeaders(); headers.add(of(""Name""), of(""value1"")); headers.add(of(""name""), of(""value2"")); headers.add(of(""NAME""), of(""value3"")); assertEquals(3, headers.size()); List values = asList(""value1"", ""value2"", ""value3""); assertEquals(values, headers.getAll(of(""NAME""))); assertEquals(values, headers.getAll(of(""name""))); assertEquals(values, headers.getAll(of(""Name""))); assertEquals(values, headers.getAll(of(""nAmE""))); } @Test public void keysShouldBeCaseInsensitiveInHeadersEquals() { DefaultHttpHeaders headers1 = new DefaultHttpHeaders(); headers1.add(of(""name1""), asList(""value1"", ""value2"", ""value3"")); headers1.add(of(""nAmE2""), of(""value4"")); DefaultHttpHeaders headers2 = new DefaultHttpHeaders(); headers2.add(of(""naMe1""), asList(""value1"", ""value2"", ""value3"")); headers2.add(of(""NAME2""), of(""value4"")); assertEquals(headers1, headers1); assertEquals(headers2, headers2); assertEquals(headers1, headers2); assertEquals(headers2, headers1); assertEquals(headers1.hashCode(), headers2.hashCode()); } @Test public void testStringKeyRetrievedAsAsciiString() { final HttpHeaders headers = new DefaultHttpHeaders(false); // Test adding String key and retrieving it using a AsciiString key final String connection = ""keep-alive""; headers.add(of(""Connection""), connection); // Passes final String value = headers.getAsString(HttpHeaderNames.CONNECTION.toString()); assertNotNull(value); assertEquals(connection, value); // Passes final String value2 = headers.getAsString(HttpHeaderNames.CONNECTION); assertNotNull(value2); assertEquals(connection, value2); } @Test public void testAsciiStringKeyRetrievedAsString() { final HttpHeaders headers = new DefaultHttpHeaders(false); // Test adding AsciiString key and retrieving it using a String key final String cacheControl = ""no-cache""; headers.add(HttpHeaderNames.CACHE_CONTROL, cacheControl); final String value = headers.getAsString(HttpHeaderNames.CACHE_CONTROL); assertNotNull(value); assertEquals(cacheControl, value); final String value2 = headers.getAsString(HttpHeaderNames.CACHE_CONTROL.toString()); assertNotNull(value2); assertEquals(cacheControl, value2); } @Test public void testRemoveTransferEncodingIgnoreCase() { HttpMessage [MASK] = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); [MASK] .headers().set(HttpHeaderNames.TRANSFER_ENCODING, ""Chunked""); assertFalse( [MASK] .headers().isEmpty()); HttpUtil.setTransferEncodingChunked( [MASK] , false); assertTrue( [MASK] .headers().isEmpty()); } // Test for https://github.com/netty/netty/issues/1690 @Test public void testGetOperations() { HttpHeaders headers = new DefaultHttpHeaders(); headers.add(of(""Foo""), of(""1"")); headers.add(of(""Foo""), of(""2"")); assertEquals(""1"", headers.get(of(""Foo""))); List values = headers.getAll(of(""Foo"")); assertEquals(2, values.size()); assertEquals(""1"", values.get(0)); assertEquals(""2"", values.get(1)); } @Test public void testEqualsIgnoreCase() { assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true)); assertThat(AsciiString.contentEqualsIgnoreCase(null, ""foo""), is(false)); assertThat(AsciiString.contentEqualsIgnoreCase(""bar"", null), is(false)); assertThat(AsciiString.contentEqualsIgnoreCase(""FoO"", ""fOo""), is(true)); } @Test public void testSetNullHeaderValueValidate() { final HttpHeaders headers = new DefaultHttpHeaders(true); assertThrows(NullPointerException.class, new Executable() { @Override public void execute() { headers.set(of(""test""), (CharSequence) null); } }); } @Test public void testSetNullHeaderValueNotValidate() { final HttpHeaders headers = new DefaultHttpHeaders(false); assertThrows(NullPointerException.class, new Executable() { @Override public void execute() { headers.set(of(""test""), (CharSequence) null); } }); } @Test public void addCharSequences() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); headers.add(HEADER_NAME, HeaderValue.THREE.asList()); assertDefaultValues(headers, HeaderValue.THREE); } @Test public void addIterable() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); headers.add(HEADER_NAME, HeaderValue.THREE.asList()); assertDefaultValues(headers, HeaderValue.THREE); } @Test public void addObjects() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); headers.add(HEADER_NAME, HeaderValue.THREE.asList()); assertDefaultValues(headers, HeaderValue.THREE); } @Test public void setCharSequences() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); headers.set(HEADER_NAME, HeaderValue.THREE.asList()); assertDefaultValues(headers, HeaderValue.THREE); } @Test public void setIterable() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); headers.set(HEADER_NAME, HeaderValue.THREE.asList()); assertDefaultValues(headers, HeaderValue.THREE); } @Test public void setObjectObjects() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); headers.set(HEADER_NAME, HeaderValue.THREE.asList()); assertDefaultValues(headers, HeaderValue.THREE); } @Test public void setObjectIterable() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); headers.set(HEADER_NAME, HeaderValue.THREE.asList()); assertDefaultValues(headers, HeaderValue.THREE); } @Test public void setCharSequenceValidatesValue() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, new Executable() { @Override public void execute() throws Throwable { headers.set(HEADER_NAME, ILLEGAL_VALUE); } }); assertTrue(exception.getMessage().contains(HEADER_NAME)); } @Test public void setIterableValidatesValue() { final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, new Executable() { @Override public void execute() throws Throwable { headers.set(HEADER_NAME, Collections.singleton(ILLEGAL_VALUE)); } }); assertTrue(exception.getMessage().contains(HEADER_NAME)); } @Test public void toStringOnEmptyHeaders() { assertEquals(""DefaultHttpHeaders[]"", newDefaultDefaultHttpHeaders().toString()); } @Test public void toStringOnSingleHeader() { assertEquals(""DefaultHttpHeaders[foo: bar]"", newDefaultDefaultHttpHeaders() .add(""foo"", ""bar"") .toString()); } @Test public void toStringOnMultipleHeaders() { assertEquals(""DefaultHttpHeaders[foo: bar, baz: qix]"", newDefaultDefaultHttpHeaders() .add(""foo"", ""bar"") .add(""baz"", ""qix"") .toString()); } @Test public void providesHeaderNamesAsArray() throws Exception { Set nettyHeaders = new DefaultHttpHeaders() .add(HttpHeaderNames.CONTENT_LENGTH, 10) .names(); String[] namesArray = nettyHeaders.toArray(EmptyArrays.EMPTY_STRINGS); assertArrayEquals(namesArray, new String[] { HttpHeaderNames.CONTENT_LENGTH.toString() }); } @Test public void names() { HttpHeaders headers = new DefaultHttpHeaders(true) .add(ACCEPT, APPLICATION_JSON) .add(CONTENT_LENGTH, ZERO) .add(CONNECTION, CLOSE); assertFalse(headers.isEmpty()); assertEquals(3, headers.size()); Set names = headers.names(); assertEquals(3, names.size()); assertTrue(names.contains(ACCEPT.toString())); assertTrue(names.contains(CONTENT_LENGTH.toString())); assertTrue(names.contains(CONNECTION.toString())); } @Test public void testContainsName() { HttpHeaders headers = new DefaultHttpHeaders(true) .add(CONTENT_LENGTH, ""36""); assertTrue(headers.contains(""Content-Length"")); assertTrue(headers.contains(""content-length"")); assertTrue(headers.contains(CONTENT_LENGTH)); headers.remove(CONTENT_LENGTH); assertFalse(headers.contains(""Content-Length"")); assertFalse(headers.contains(""content-length"")); assertFalse(headers.contains(CONTENT_LENGTH)); assertFalse(headers.contains(""non-existent-name"")); assertFalse(headers.contains(new AsciiString(""non-existent-name""))); } private static void assertDefaultValues(final DefaultHttpHeaders headers, final HeaderValue headerValue) { assertTrue(contentEquals(headerValue.asList().get(0), headers.get(HEADER_NAME))); List expected = headerValue.asList(); List actual = headers.getAll(HEADER_NAME); assertEquals(expected.size(), actual.size()); Iterator eItr = expected.iterator(); Iterator aItr = actual.iterator(); while (eItr.hasNext()) { assertTrue(contentEquals(eItr.next(), aItr.next())); } } private static DefaultHttpHeaders newDefaultDefaultHttpHeaders() { return new DefaultHttpHeaders(true); } } ","message " "/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2019 Guardsquare NV * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.preverify; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; import proguard.classfile.editor.CodeAttributeComposer; import proguard.classfile.instruction.*; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.SimplifiedVisitor; import proguard.classfile.visitor.*; import proguard.optimize.peephole.BranchTargetFinder; /** * This AttributeVisitor inlines local subroutines (jsr/ret) in the code * attributes that it visits. * * @author Eric Lafortune */ public class CodeSubroutineInliner extends SimplifiedVisitor implements AttributeVisitor, InstructionVisitor, ExceptionInfoVisitor { //* private static final boolean DEBUG = false; /*/ private static boolean DEBUG = System.getProperty(""csi"") != null; //*/ private final BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(true, true, true); private ExceptionInfoVisitor subroutineExceptionInliner = this; private int clipStart = 0; private int clipEnd = Integer.MAX_VALUE; // Implementations for AttributeVisitor. public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // DEBUG = // clazz.getName().equals(""abc/Def"") && // method.getName(clazz).equals(""abc""); // CodeAttributeComposer.DEBUG = DEBUG; // TODO: Remove this when the subroutine inliner has stabilized. // Catch any unexpected exceptions from the actual visiting method. try { // Process the code. visitCodeAttribute0(clazz, method, codeAttribute); } catch (RuntimeException ex) { System.err.println(""Unexpected error while inlining subroutines:""); System.err.println("" Class = [""+clazz.getName()+""]""); System.err.println("" Method = [""+method.getName(clazz)+method.getDescriptor(clazz)+""]""); System.err.println("" Exception = [""+ex.getClass().getName()+""] (""+ex.getMessage()+"")""); if (DEBUG) { method.accept(clazz, new ClassPrinter()); } throw ex; } } public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute) { branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute); // Don't bother if there aren't any subroutines anyway. if (!branchTargetFinder.containsSubroutines()) { return; } if (DEBUG) { System.out.println(""SubroutineInliner: processing [""+clazz.getName()+"".""+method.getName(clazz)+method.getDescriptor(clazz)+""]""); } // Append the body of the code. codeAttributeComposer.reset(); codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); // Copy the non-subroutine instructions. int [MASK] = 0; while ( [MASK] < codeAttribute.u4codeLength) { Instruction instruction = InstructionFactory.create(codeAttribute.code, [MASK] ); int instructionLength = instruction.length( [MASK] ); // Is this a returning subroutine? if (branchTargetFinder.isSubroutine( [MASK] ) && branchTargetFinder.isSubroutineReturning( [MASK] )) { // Skip the subroutine. if (DEBUG) { System.out.println("" Skipping original subroutine instruction ""+instruction.toString( [MASK] )); } // Append a label at this [MASK] instead. codeAttributeComposer.appendLabel( [MASK] ); } else { // Copy the instruction, inlining any subroutine call recursively. instruction.accept(clazz, method, codeAttribute, [MASK] , this); } [MASK] += instructionLength; } // Copy the exceptions. Note that exceptions with empty try blocks // are automatically removed. codeAttribute.exceptionsAccept(clazz, method, subroutineExceptionInliner); if (DEBUG) { System.out.println("" Appending label after code at [""+ [MASK] +""]""); } // Append a label just after the code. codeAttributeComposer.appendLabel(codeAttribute.u4codeLength); // End and update the code attribute. codeAttributeComposer.endCodeFragment(); codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute); } /** * Appends the specified subroutine. */ private void inlineSubroutine(Clazz clazz, Method method, CodeAttribute codeAttribute, int subroutineInvocationOffset, int subroutineStart) { int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart); if (DEBUG) { System.out.println("" Inlining subroutine [""+subroutineStart+"" -> ""+subroutineEnd+""] at [""+subroutineInvocationOffset+""]""); } // Don't go inlining exceptions that are already applicable to this // subroutine invocation. ExceptionInfoVisitor oldSubroutineExceptionInliner = subroutineExceptionInliner; int oldClipStart = clipStart; int oldClipEnd = clipEnd; subroutineExceptionInliner = new ExceptionExcludedOffsetFilter(subroutineInvocationOffset, subroutineExceptionInliner); clipStart = subroutineStart; clipEnd = subroutineEnd; codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); // Copy the subroutine instructions, inlining any subroutine calls // recursively. codeAttribute.instructionsAccept(clazz, method, subroutineStart, subroutineEnd, this); if (DEBUG) { System.out.println("" Appending label after inlined subroutine at [""+subroutineEnd+""]""); } // Append a label just after the code. codeAttributeComposer.appendLabel(subroutineEnd); // Inline the subroutine exceptions. codeAttribute.exceptionsAccept(clazz, method, subroutineStart, subroutineEnd, subroutineExceptionInliner); // We can again inline exceptions that are applicable to this // subroutine invocation. subroutineExceptionInliner = oldSubroutineExceptionInliner; clipStart = oldClipStart; clipEnd = oldClipEnd; codeAttributeComposer.endCodeFragment(); } // Implementations for InstructionVisitor. public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int [MASK] , Instruction instruction) { if (branchTargetFinder.isSubroutineStart( [MASK] )) { if (DEBUG) { System.out.println("" Replacing first subroutine instruction ""+instruction.toString( [MASK] )+"" by a label""); } // Append a label at this [MASK] instead of saving the subroutine // return address. codeAttributeComposer.appendLabel( [MASK] ); } else { // Append the instruction. codeAttributeComposer.appendInstruction( [MASK] , instruction); } } public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int [MASK] , VariableInstruction variableInstruction) { byte opcode = variableInstruction.opcode; if (opcode == InstructionConstants.OP_RET) { // Is the return instruction the last instruction of the subroutine? if (branchTargetFinder.subroutineEnd( [MASK] ) == [MASK] + variableInstruction.length( [MASK] )) { if (DEBUG) { System.out.println("" Replacing subroutine return at [""+ [MASK] +""] by a label""); } // Append a label at this [MASK] instead of the subroutine return. codeAttributeComposer.appendLabel( [MASK] ); } else { if (DEBUG) { System.out.println("" Replacing subroutine return at [""+ [MASK] +""] by a simple branch""); } // Replace the instruction by a branch. Instruction replacementInstruction = new BranchInstruction(InstructionConstants.OP_GOTO, branchTargetFinder.subroutineEnd( [MASK] ) - [MASK] ); codeAttributeComposer.appendInstruction( [MASK] , replacementInstruction); } } else if (branchTargetFinder.isSubroutineStart( [MASK] )) { if (DEBUG) { System.out.println("" Replacing first subroutine instruction ""+variableInstruction.toString( [MASK] )+"" by a label""); } // Append a label at this [MASK] instead of saving the subroutine // return address. codeAttributeComposer.appendLabel( [MASK] ); } else { // Append the instruction. codeAttributeComposer.appendInstruction( [MASK] , variableInstruction); } } public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int [MASK] , BranchInstruction branchInstruction) { byte opcode = branchInstruction.opcode; if (opcode == InstructionConstants.OP_JSR || opcode == InstructionConstants.OP_JSR_W) { int branchOffset = branchInstruction.branchOffset; int branchTarget = [MASK] + branchOffset; // Is the subroutine ever returning? if (branchTargetFinder.isSubroutineReturning(branchTarget)) { // Append a label at this [MASK] instead of the subroutine invocation. codeAttributeComposer.appendLabel( [MASK] ); // Inline the invoked subroutine. inlineSubroutine(clazz, method, codeAttribute, [MASK] , branchTarget); } else { if (DEBUG) { System.out.println(""Replacing subroutine invocation at [""+ [MASK] +""] by a simple branch""); } // Replace the subroutine invocation by a simple branch. Instruction replacementInstruction = new BranchInstruction(InstructionConstants.OP_GOTO, branchOffset); codeAttributeComposer.appendInstruction( [MASK] , replacementInstruction); } } else { // Append the instruction. codeAttributeComposer.appendInstruction( [MASK] , branchInstruction); } } // Implementations for ExceptionInfoVisitor. public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) { int startPC = Math.max(exceptionInfo.u2startPC, clipStart); int endPC = Math.min(exceptionInfo.u2endPC, clipEnd); int handlerPC = exceptionInfo.u2handlerPC; int catchType = exceptionInfo.u2catchType; // Exclude any subroutine invocations that jump out of the try block, // by adding a try block before (and later on, after) each invocation. for (int [MASK] = startPC; [MASK] < endPC; [MASK] ++) { if (branchTargetFinder.isSubroutineInvocation( [MASK] )) { Instruction instruction = InstructionFactory.create(codeAttribute.code, [MASK] ); int instructionLength = instruction.length( [MASK] ); // Is it a subroutine invocation? if (!exceptionInfo.isApplicable( [MASK] + ((BranchInstruction)instruction).branchOffset)) { if (DEBUG) { System.out.println("" Appending extra exception [""+startPC+"" -> ""+ [MASK] +""] -> ""+handlerPC); } // Append a try block that ends before the subroutine invocation. codeAttributeComposer.appendException(new ExceptionInfo(startPC, [MASK] , handlerPC, catchType)); // The next try block will start after the subroutine invocation. startPC = [MASK] + instructionLength; } } } if (DEBUG) { if (startPC == exceptionInfo.u2startPC && endPC == exceptionInfo.u2endPC) { System.out.println("" Appending exception [""+startPC+"" -> ""+endPC+""] -> ""+handlerPC); } else { System.out.println("" Appending clipped exception [""+exceptionInfo.u2startPC+"" -> ""+exceptionInfo.u2endPC+""] ~> [""+startPC+"" -> ""+endPC+""] -> ""+handlerPC); } } // Append the exception. Note that exceptions with empty try blocks // are automatically ignored. codeAttributeComposer.appendException(new ExceptionInfo(startPC, endPC, handlerPC, catchType)); } } ","offset " "/* Copyright 2010, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS"" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.google.refine; import java.awt.Desktop; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.BindException; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.swing.JFrame; import org.apache.commons.lang.SystemUtils; import org.apache.log4j.Level; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.thread.ThreadPool; import com.google.util.threads.ThreadPoolExecutorAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Main class for Refine server application. Starts an instance of the Jetty HTTP server / servlet container (inner * class Refine Server). */ public class Refine { static private final String DEFAULT_IFACE = ""127.0.0.1""; static private final int DEFAULT_PORT = 3333; static private int port; static private String host; static private String iface; final static Logger logger = LoggerFactory.getLogger(""refine""); public static void main(String[] args) throws Exception { // tell jetty to use SLF4J for logging instead of its own stuff System.setProperty(""VERBOSE"", ""false""); System.setProperty(""org.eclipse.jetty.log.class"", ""org.eclipse.jetty.util.log.Slf4jLog""); // tell macosx to keep the menu associated with the screen and what the app title is System.setProperty(""apple.laf.useScreenMenuBar"", ""true""); System.setProperty(""com.apple.eawt.CocoaComponent.CompatibilityMode"", ""false""); System.setProperty(""com.apple.mrj.application.apple.menu.about.name"", ""OpenRefine""); // tell the signpost library to log // System.setProperty(""debug"",""true""); // set the log verbosity level org.apache.log4j.Logger.getRootLogger().setLevel(Level.toLevel(Configurations.get(""refine.verbosity"", ""info""))); port = Configurations.getInteger(""refine.port"", DEFAULT_PORT); iface = Configurations.get(""refine.interface"", DEFAULT_IFACE); host = Configurations.get(""refine.host"", iface); if (""0.0.0.0"".equals(host)) { host = ""*""; } System.setProperty(""refine.display.new.version.notice"", Configurations.get(""refine.display.new.version.notice"", ""true"")); Refine refine = new Refine(); refine.init(args); } public void init(String[] args) throws Exception { RefineServer server = new RefineServer(); server.init(iface, port, host); boolean headless = Configurations.getBoolean(""refine.headless"", false); if (headless) { System.setProperty(""java.awt.headless"", ""true""); logger.info(""Running in headless mode""); } else { try { RefineClient client = new RefineClient(); if (""*"".equals(host)) { if (""0.0.0.0"".equals(iface)) { logger.warn(""No refine.host specified while binding to interface 0.0.0.0, guessing localhost.""); client.init(""localhost"", port); } else { client.init(iface, port); } } else { client.init(host, port); } } catch (Exception e) { logger.warn(""Sorry, some error prevented us from launching the browser for you.\n\n Point your browser to http://"" + host + "":"" + port + ""/ to start using Refine.""); } } // hook up the signal handlers Runtime.getRuntime().addShutdownHook( new Thread(new ShutdownSignalHandler(server))); server.join(); } } /* -------------- Refine Server ----------------- */ class RefineServer extends Server { final static Logger logger = LoggerFactory.getLogger(""refine_server""); public RefineServer() { super(createThreadPool()); } private static ThreadPool createThreadPool() { int maxThreads = Configurations.getInteger(""refine.queue.size"", 30); int maxQueue = Configurations.getInteger(""refine.queue.max_size"", 300); long keepAliveTime = Configurations.getInteger(""refine.queue.idle_time"", 60); LinkedBlockingQueue queue = new LinkedBlockingQueue(maxQueue); return new ThreadPoolExecutorAdapter(new ThreadPoolExecutor(maxThreads, maxQueue, keepAliveTime, TimeUnit.SECONDS, queue)); } private ThreadPoolExecutor threadPool; public void init(String iface, int port, String host) throws Exception { logger.info(""Starting Server bound to '"" + iface + "":"" + port + ""'""); String memory = Configurations.get(""refine.memory""); if (memory != null) { logger.info(""refine.memory size: "" + memory + "" JVM Max heap: "" + Runtime.getRuntime().maxMemory() + "" bytes""); } HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSendServerVersion(false); HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfig); ServerConnector connector = new ServerConnector(this, httpFactory); connector.setPort(port); connector.setHost(iface); connector.setIdleTimeout(Configurations.getInteger(""server.connection.max_idle_time"", 60000)); this.addConnector(connector); File webapp = new File(Configurations.get(""refine.webapp"", ""main/webapp"")); if (!isWebapp(webapp)) { webapp = new File(""main/webapp""); if (!isWebapp(webapp)) { webapp = new File(""webapp""); if (!isWebapp(webapp)) { logger.warn(""Warning: Failed to find web application at '"" + webapp.getAbsolutePath() + ""'""); System.exit(-1); } } } final String contextPath = Configurations.get(""refine.context_path"", ""/""); final int maxFormContentSize = Configurations.getInteger(""refine.max_form_content_size"", 64 * 1048576); // 64MB logger.info(""Initializing context: '"" + contextPath + ""' from '"" + webapp.getAbsolutePath() + ""'""); WebAppContext context = new WebAppContext(webapp.getAbsolutePath(), contextPath); context.setMaxFormContentSize(maxFormContentSize); if (""*"".equals(host)) { this.setHandler(context); } else { ValidateHostHandler wrapper = new ValidateHostHandler(host); wrapper.setHandler(context); this.setHandler(wrapper); } this.setStopAtShutdown(true); StatisticsHandler handler = new StatisticsHandler(); handler.setServer(this); handler.setHandler(this.getHandler()); this.addBean(handler); // Tell the server we want to try and shutdown gracefully // this means that the server will stop accepting new connections // right away but it will continue to process the ones that // are in execution for the given timeout before attempting to stop // NOTE: this is *not* a blocking method, it just sets a parameter // that _server.stop() will rely on this.setStopTimeout(30000); // Enable context autoreloading if (Configurations.getBoolean(""refine.autoreload"", false)) { scanForUpdates(webapp, context); } // start the server try { this.start(); } catch (BindException e) { logger.error(""Failed to start server - is there another copy running already on this port/address?""); throw e; } configure(context); } @Override protected void doStop() throws Exception { try { // shutdown our scheduled tasks first, if any if (threadPool != null) { threadPool.shutdown(); } Thread.sleep(3000); } catch (InterruptedException e) { // stop current thread Thread.currentThread().interrupt(); } // then let the parent stop super.doStop(); } static private boolean isWebapp(File dir) { if (dir == null) { return false; } if (!dir.exists() || !dir.canRead()) { return false; } File webXml = new File(dir, ""WEB-INF/web.xml""); return webXml.exists() && webXml.canRead(); } static private void scanForUpdates(final File contextRoot, final WebAppContext context) { List scanList = new ArrayList(); scanList.add(new File(contextRoot, ""WEB-INF/web.xml"")); findFiles("".class"", new File(contextRoot, ""WEB-INF/classes""), scanList); findFiles("".jar"", new File(contextRoot, ""WEB-INF/lib""), scanList); logger.info(""Starting autoreloading scanner... ""); Scanner scanner = new Scanner(); scanner.setScanInterval(Configurations.getInteger(""refine.scanner.period"", 1)); scanner.setScanDirs(scanList); scanner.setReportExistingFilesOnStartup(false); scanner.addListener(new Scanner.BulkListener() { @Override public void filesChanged(@SuppressWarnings(""rawtypes"") List changedFiles) { try { logger.info(""Stopping context: "" + contextRoot.getAbsolutePath()); context.stop(); logger.info(""Starting context: "" + contextRoot.getAbsolutePath()); context.start(); configure(context); } catch (Exception ex) { throw new RuntimeException(ex); } } }); try { scanner.start(); } catch (Exception e) { e.printStackTrace(); } } static private void findFiles(final String extension, File baseDir, final Collection found) { baseDir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { if (pathname.isDirectory()) { findFiles(extension, pathname, found); } else if (pathname.getName().endsWith(extension)) { found.add(pathname); } return false; } }); } // inject configuration parameters in the servlets // NOTE: this is done *after* starting the server because jetty might override the init // parameters if we set them in the webapp context upon reading the web.xml file static private void configure(WebAppContext context) throws Exception { ServletHolder servlet = context.getServletHandler().getServlet(""refine""); if (servlet != null) { servlet.setInitParameter(""refine.data"", getDataDir()); servlet.setInitParameter(""butterfly.modules.path"", getDataDir() + ""/extensions""); servlet.setInitParameter(""refine.autosave"", Configurations.get(""refine.autosave"", ""5"")); // default: 5 // minutes servlet.setInitOrder(1); servlet.doStart(); } } static private String getDataDir() { String data_dir = Configurations.get(""refine.data_dir""); if (data_dir != null) { return data_dir; } File dataDir = null; File grefineDir = null; File gridworksDir = null; String os = System.getProperty(""os.name"").toLowerCase(); if (os.contains(""windows"")) { File parentDir = null; String appData = System.getenv(""APPDATA""); if (appData != null && appData.length() > 0) { // e.g., C:\Users\[userid]\AppData\Roaming parentDir = new File(appData); } else { // TODO migrate to System.getProperty(""user.home"")? String userProfile = System.getProperty(""user.home""); if (userProfile != null && userProfile.length() > 0) { // e.g., C:\Users\[userid] parentDir = new File(userProfile); } } if (parentDir == null) { parentDir = new File("".""); } dataDir = new File(parentDir, ""OpenRefine""); grefineDir = new File(new File(parentDir, ""Google""), ""Refine""); gridworksDir = new File(parentDir, ""Gridworks""); } else if (os.contains(""os x"")) { // on macosx, use ""~/Library/Application Support"" String home = System.getProperty(""user.home""); String [MASK] = (home != null) ? home + ""/Library/Application Support/OpenRefine"" : "".openrefine""; dataDir = new File( [MASK] ); String grefine_home = (home != null) ? home + ""/Library/Application Support/Google/Refine"" : "".google-refine""; grefineDir = new File(grefine_home); String gridworks_home = (home != null) ? home + ""/Library/Application Support/Gridworks"" : "".gridworks""; gridworksDir = new File(gridworks_home); } else { // most likely a UNIX flavor // start with the XDG environment // see http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html String [MASK] = System.getenv(""XDG_DATA_HOME""); if ( [MASK] == null) { // if not found, default back to ~/.local/share String home = System.getProperty(""user.home""); if (home == null) { home = "".""; } [MASK] = home + ""/.local/share""; } dataDir = new File( [MASK] + ""/openrefine""); grefineDir = new File( [MASK] + ""/google/refine""); gridworksDir = new File( [MASK] + ""/gridworks""); } // If refine data dir doesn't exist, try to find and move Google Refine or Gridworks data dir over if (!dataDir.exists()) { if (grefineDir.exists()) { if (gridworksDir.exists()) { logger.warn(""Found both Gridworks: "" + gridworksDir + "" & Googld Refine dirs "" + grefineDir); } if (grefineDir.renameTo(dataDir)) { logger.info(""Renamed Google Refine directory "" + grefineDir + "" to "" + dataDir); } else { logger.error(""FAILED to rename Google Refine directory "" + grefineDir + "" to "" + dataDir); } } else if (gridworksDir.exists()) { if (gridworksDir.renameTo(dataDir)) { logger.info(""Renamed Gridworks directory "" + gridworksDir + "" to "" + dataDir); } else { logger.error(""FAILED to rename Gridworks directory "" + gridworksDir + "" to "" + dataDir); } } } // Either rename failed or nothing to rename - create a new one if (!dataDir.exists()) { logger.info(""Creating new workspace directory "" + dataDir); if (!dataDir.mkdirs()) { logger.error(""FAILED to create new workspace directory "" + dataDir); } } return dataDir.getAbsolutePath(); } /** * For Windows file paths that contain user IDs with non ASCII characters, those characters might get replaced with * ?. We need to use the environment APPDATA value to substitute back the original user ID. */ static private String fixWindowsUnicodePath(String path) { int q = path.indexOf('?'); if (q < 0) { return path; } int pathSep = path.indexOf(File.separatorChar, q); String goodPath = System.getenv(""APPDATA""); if (goodPath == null || goodPath.length() == 0) { goodPath = System.getenv(""USERPROFILE""); if (!goodPath.endsWith(File.separator)) { goodPath = goodPath + File.separator; } } int goodPathSep = goodPath.indexOf(File.separatorChar, q); return path.substring(0, q) + goodPath.substring(q, goodPathSep) + path.substring(pathSep); } } /* -------------- Refine Client ----------------- */ class RefineClient extends JFrame implements ActionListener { private static final long serialVersionUID = 7886547342175227132L; final static Logger logger = LoggerFactory.getLogger(""refine-client""); private URI uri; public void init(String host, int port) throws Exception { uri = new URI(""http://"" + host + "":"" + port + ""/""); openBrowser(); } @Override public void actionPerformed(ActionEvent e) { String item = e.getActionCommand(); if (item.startsWith(""Open"")) { openBrowser(); } } private void openBrowser() { if (!Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { try { openBrowserFallback(); } catch (IOException e) { throw new RuntimeException(e); } } else { try { Desktop.getDesktop().browse(uri); } catch (IOException e) { throw new RuntimeException(e); } } } private void openBrowserFallback() throws IOException { Runtime rt = Runtime.getRuntime(); if (SystemUtils.IS_OS_WINDOWS) { rt.exec(new String[] { ""rundll32 "", ""url.dll,FileProtocolHandler "", String.valueOf(uri) }); } else if (SystemUtils.IS_OS_MAC_OSX) { rt.exec(new String[] { ""open "", String.valueOf(uri) }); } else if (SystemUtils.IS_OS_LINUX) { rt.exec(new String[] { ""xdg-open"", String.valueOf(uri) }); } else { logger.warn(""Java Desktop class not supported on this platform. Please open %s in your browser"", uri.toString()); } } } class ShutdownSignalHandler implements Runnable { private Server _server; public ShutdownSignalHandler(Server server) { this._server = server; } @Override public void run() { try { _server.stop(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } ","data_home " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.tools.particleeditor; import java.awt.FileDialog; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; import javax.swing.ButtonGroup; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.ListSelectionModel; import com.badlogic.gdx.graphics.g2d.ParticleEmitter; import com.badlogic.gdx.graphics.g2d.ParticleEmitter.SpriteMode; import com.badlogic.gdx.utils.Array; class ImagePanel extends EditorPanel { JPanel imagesPanel; JList imageList; DefaultListModel imageListModel; String lastDir; public ImagePanel (final ParticleEditor editor, String name, String description) { super(null, name, description); JPanel contentPanel = getContentPanel(); { JPanel buttonsPanel = new JPanel(new GridLayout(3, 1)); contentPanel.add(buttonsPanel, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); JButton addButton = new JButton(""Add""); buttonsPanel.add(addButton); addButton.addActionListener(new ActionListener() { public void actionPerformed (ActionEvent event) { FileDialog dialog = new FileDialog(editor, ""Open Image"", FileDialog.LOAD); if (lastDir != null) dialog.setDirectory(lastDir); dialog.setMultipleMode(true); dialog.setVisible(true); final File[] files = dialog.getFiles(); final String dir = dialog.getDirectory(); if (dir == null || files == null) return; lastDir = dir; final ParticleEmitter emitter = editor.getEmitter(); for (File file : files) { emitter.getImagePaths().add(file.getAbsolutePath()); } emitter.getSprites().clear(); updateImageList(emitter.getImagePaths()); } }); JButton defaultButton = new JButton(""Default""); buttonsPanel.add(defaultButton); defaultButton.addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { final ParticleEmitter emitter = editor.getEmitter(); emitter.setImagePaths(new Array(new String[] {ParticleEditor.DEFAULT_PARTICLE})); emitter.getSprites().clear(); updateImageList(emitter.getImagePaths()); } }); JButton defaultPremultButton = new JButton(""Default (Premultiplied Alpha)""); buttonsPanel.add(defaultPremultButton); defaultPremultButton.addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { final ParticleEmitter emitter = editor.getEmitter(); emitter.setImagePaths(new Array(new String[] {ParticleEditor.DEFAULT_PREMULT_PARTICLE})); emitter.getSprites().clear(); updateImageList(emitter.getImagePaths()); } }); } { JPanel modesPanel = new JPanel(new GridLayout(4, 1)); contentPanel.add(modesPanel, new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); JLabel label = new JLabel(""Sprite mode:""); modesPanel.add(label); ButtonGroup checkboxGroup = new ButtonGroup(); JRadioButton singleCheckbox = new JRadioButton(""Single"", editor.getEmitter().getSpriteMode() == SpriteMode.single); modesPanel.add(singleCheckbox); checkboxGroup.add(singleCheckbox); singleCheckbox.addItemListener(new ItemListener() { @Override public void itemStateChanged (ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { editor.getEmitter().setSpriteMode(SpriteMode.single); } } }); JRadioButton randomCheckbox = new JRadioButton(""Random"", editor.getEmitter().getSpriteMode() == SpriteMode.random); modesPanel.add(randomCheckbox); checkboxGroup.add(randomCheckbox); randomCheckbox.addItemListener(new ItemListener() { @Override public void itemStateChanged (ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { editor.getEmitter().setSpriteMode(SpriteMode.random); } } }); JRadioButton animatedCheckbox = new JRadioButton(""Animated"", editor.getEmitter().getSpriteMode() == SpriteMode.animated); modesPanel.add(animatedCheckbox); checkboxGroup.add(animatedCheckbox); animatedCheckbox.addItemListener(new ItemListener() { @Override public void itemStateChanged (ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { editor.getEmitter().setSpriteMode(SpriteMode.animated); } } }); } { imagesPanel = new JPanel(new GridBagLayout()); contentPanel.add(imagesPanel, new GridBagConstraints(2, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); imageListModel = new DefaultListModel(); imageList = new JList(imageListModel); imageList.setFixedCellWidth(250); imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); imagesPanel.add(imageList, new GridBagConstraints(0, 0, 1, 3, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); JButton upButton = new JButton(""\u2191""); imagesPanel.add(upButton, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); upButton.addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { int index = imageList.getSelectedIndex(); if (index <= 0) return; final ParticleEmitter emitter = editor.getEmitter(); String imagePath = emitter.getImagePaths().removeIndex(index); emitter.getImagePaths().insert(index - 1, imagePath); emitter.getSprites().clear(); updateImageList(emitter.getImagePaths()); imageList.setSelectedIndex(index - 1); } }); JButton downButton = new JButton(""\u2193""); imagesPanel.add(downButton, new GridBagConstraints(1, 1, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); downButton.addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { int index = imageList.getSelectedIndex(); if (index < 0 || index >= imageList.getModel().getSize() - 1) return; final ParticleEmitter emitter = editor.getEmitter(); String imagePath = emitter.getImagePaths().removeIndex(index); emitter.getImagePaths().insert(index + 1, imagePath); emitter.getSprites().clear(); updateImageList(emitter.getImagePaths()); imageList.setSelectedIndex(index + 1); } }); JButton [MASK] = new JButton(""X""); imagesPanel.add( [MASK] , new GridBagConstraints(1, 2, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); [MASK] .addActionListener(new ActionListener() { @Override public void actionPerformed (ActionEvent e) { int index = imageList.getSelectedIndex(); if (index < 0) return; final ParticleEmitter emitter = editor.getEmitter(); Array imagePaths = emitter.getImagePaths(); imagePaths.removeIndex(index); if (imagePaths.size == 0) imagePaths.add(ParticleEditor.DEFAULT_PARTICLE); emitter.getSprites().clear(); updateImageList(imagePaths); } }); } updateImageList(editor.getEmitter().getImagePaths()); } public void updateImageList (Array imagePaths) { if (imagePaths != null && imagePaths.size > 0) { imagesPanel.setVisible(true); imageListModel.removeAllElements(); for (String imagePath : imagePaths) { imageListModel.addElement(new File(imagePath).getName()); } } else { imagesPanel.setVisible(false); } revalidate(); } } ","removeButton " "/* * Copyright (c) 2016 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.creation.bytebuddy; import static net.bytebuddy.matcher.ElementMatchers.isVisibleTo; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import static net.bytebuddy.matcher.ElementMatchers.isStatic; import static net.bytebuddy.matcher.ElementMatchers.not; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.SoftReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.concurrent.Callable; import java.util.function.Predicate; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.AsmVisitorWrapper; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.field.FieldList; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.method.ParameterDescription; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.scaffold.MethodGraph; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.bind.annotation.Argument; import net.bytebuddy.implementation.bind.annotation.This; import net.bytebuddy.implementation.bytecode.StackSize; import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.jar.asm.Label; import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; import net.bytebuddy.jar.asm.Type; import net.bytebuddy.pool.TypePool; import net.bytebuddy.utility.OpenedClassReader; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.configuration.plugins.Plugins; import org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher; import org.mockito.internal.debugging.LocationFactory; import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter; import org.mockito.internal.invocation.RealMethod; import org.mockito.internal.invocation.SerializableMethod; import org.mockito.internal.invocation.mockref.MockReference; import org.mockito.internal.invocation.mockref.MockWeakReference; import org.mockito.internal.util.concurrent.DetachedThreadLocal; import org.mockito.internal.util.concurrent.WeakConcurrentMap; import org.mockito.plugins.MemberAccessor; public class MockMethodAdvice extends MockMethodDispatcher { private final WeakConcurrentMap interceptors; private final DetachedThreadLocal, MockMethodInterceptor>> mockedStatics; private final String identifier; private final SelfCallInfo selfCallInfo = new SelfCallInfo(); private final MethodGraph.Compiler compiler = MethodGraph.Compiler.Default.forJavaHierarchy(); private final WeakConcurrentMap, SoftReference> graphs = new WeakConcurrentMap.WithInlinedExpunction<>(); private final Predicate> isMockConstruction; private final ConstructionCallback onConstruction; public MockMethodAdvice( WeakConcurrentMap interceptors, DetachedThreadLocal, MockMethodInterceptor>> mockedStatics, String identifier, Predicate> isMockConstruction, ConstructionCallback onConstruction) { this.interceptors = interceptors; this.mockedStatics = mockedStatics; this.onConstruction = onConstruction; this.identifier = identifier; this.isMockConstruction = isMockConstruction; } @SuppressWarnings(""unused"") @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) private static Callable enter( @Identifier String identifier, @Advice.This Object mock, @Advice.Origin Method origin, @Advice.AllArguments Object[] arguments) throws Throwable { MockMethodDispatcher dispatcher = MockMethodDispatcher.get(identifier, mock); if (dispatcher == null || !dispatcher.isMocked(mock) || dispatcher.isOverridden(mock, origin)) { return null; } else { return dispatcher.handle(mock, origin, arguments); } } @SuppressWarnings({""unused"", ""UnusedAssignment""}) @Advice.OnMethodExit private static void exit( @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object [MASK] , @Advice.Enter Callable mocked) throws Throwable { if (mocked != null) { [MASK] = mocked.call(); } } @Override public Callable handle(Object instance, Method origin, Object[] arguments) throws Throwable { MockMethodInterceptor interceptor = interceptors.get(instance); if (interceptor == null) { return null; } RealMethod realMethod; if (instance instanceof Serializable) { realMethod = new SerializableRealMethodCall(identifier, origin, instance, arguments); } else { realMethod = new RealMethodCall(selfCallInfo, origin, instance, arguments); } return new ReturnValueWrapper( interceptor.doIntercept( instance, origin, arguments, realMethod, LocationFactory.create(true))); } @Override public Callable handleStatic(Class type, Method origin, Object[] arguments) throws Throwable { Map, MockMethodInterceptor> interceptors = mockedStatics.get(); if (interceptors == null || !interceptors.containsKey(type)) { return null; } return new ReturnValueWrapper( interceptors .get(type) .doIntercept( type, origin, arguments, new StaticMethodCall(selfCallInfo, type, origin, arguments), LocationFactory.create(true))); } @Override public Object handleConstruction( Class type, Object object, Object[] arguments, String[] parameterTypeNames) { return onConstruction.apply(type, object, arguments, parameterTypeNames); } @Override public boolean isMock(Object instance) { // We need to exclude 'interceptors.target' explicitly to avoid a recursive check on whether // the map is a mock object what requires reading from the map. return instance != interceptors.target && interceptors.containsKey(instance); } @Override public boolean isMocked(Object instance) { return isMock(instance) && selfCallInfo.checkSelfCall(instance); } @Override public boolean isMockedStatic(Class type) { if (!selfCallInfo.checkSelfCall(type)) { return false; } Map, ?> interceptors = mockedStatics.get(); return interceptors != null && interceptors.containsKey(type); } @Override public boolean isOverridden(Object instance, Method origin) { SoftReference reference = graphs.get(instance.getClass()); MethodGraph methodGraph = reference == null ? null : reference.get(); if (methodGraph == null) { methodGraph = compiler.compile( (TypeDefinition) TypeDescription.ForLoadedType.of(instance.getClass())); graphs.put(instance.getClass(), new SoftReference<>(methodGraph)); } MethodGraph.Node node = methodGraph.locate( new MethodDescription.ForLoadedMethod(origin).asSignatureToken()); return !node.getSort().isResolved() || !node.getRepresentative() .asDefined() .getDeclaringType() .represents(origin.getDeclaringClass()); } @Override public boolean isConstructorMock(Class type) { return isMockConstruction.test(type); } private static class RealMethodCall implements RealMethod { private final SelfCallInfo selfCallInfo; private final Method origin; private final MockWeakReference instanceRef; private final Object[] arguments; private RealMethodCall( SelfCallInfo selfCallInfo, Method origin, Object instance, Object[] arguments) { this.selfCallInfo = selfCallInfo; this.origin = origin; this.instanceRef = new MockWeakReference<>(instance); this.arguments = arguments; } @Override public boolean isInvokable() { return true; } @Override public Object invoke() throws Throwable { selfCallInfo.set(instanceRef.get()); return tryInvoke(origin, instanceRef.get(), arguments); } } private static class SerializableRealMethodCall implements RealMethod { private final String identifier; private final SerializableMethod origin; private final MockReference instanceRef; private final Object[] arguments; private SerializableRealMethodCall( String identifier, Method origin, Object instance, Object[] arguments) { this.origin = new SerializableMethod(origin); this.identifier = identifier; this.instanceRef = new MockWeakReference<>(instance); this.arguments = arguments; } @Override public boolean isInvokable() { return true; } @Override public Object invoke() throws Throwable { Method method = origin.getJavaMethod(); MockMethodDispatcher mockMethodDispatcher = MockMethodDispatcher.get(identifier, instanceRef.get()); if (!(mockMethodDispatcher instanceof MockMethodAdvice)) { throw new MockitoException(""Unexpected dispatcher for advice-based super call""); } Object previous = ((MockMethodAdvice) mockMethodDispatcher) .selfCallInfo.replace(instanceRef.get()); try { return tryInvoke(method, instanceRef.get(), arguments); } finally { ((MockMethodAdvice) mockMethodDispatcher).selfCallInfo.set(previous); } } } private static class StaticMethodCall implements RealMethod { private final SelfCallInfo selfCallInfo; private final Class type; private final Method origin; private final Object[] arguments; private StaticMethodCall( SelfCallInfo selfCallInfo, Class type, Method origin, Object[] arguments) { this.selfCallInfo = selfCallInfo; this.type = type; this.origin = origin; this.arguments = arguments; } @Override public boolean isInvokable() { return true; } @Override public Object invoke() throws Throwable { selfCallInfo.set(type); return tryInvoke(origin, null, arguments); } } private static Object tryInvoke(Method origin, Object instance, Object[] arguments) throws Throwable { MemberAccessor accessor = Plugins.getMemberAccessor(); try { return accessor.invoke(origin, instance, arguments); } catch (InvocationTargetException exception) { Throwable cause = exception.getCause(); new ConditionalStackTraceFilter() .filter(removeRecursiveCalls(cause, origin.getDeclaringClass())); throw cause; } } static Throwable removeRecursiveCalls(final Throwable cause, final Class declaringClass) { final List uniqueStackTraceItems = new ArrayList<>(); final List indexesToBeRemoved = new ArrayList<>(); for (StackTraceElement element : cause.getStackTrace()) { final String key = element.getClassName() + element.getLineNumber(); final int elementIndex = uniqueStackTraceItems.lastIndexOf(key); uniqueStackTraceItems.add(key); if (elementIndex > -1 && declaringClass.getName().equals(element.getClassName())) { indexesToBeRemoved.add(elementIndex); } } final List adjustedList = new ArrayList<>(Arrays.asList(cause.getStackTrace())); indexesToBeRemoved.stream() .sorted(Comparator.reverseOrder()) .mapToInt(Integer::intValue) .forEach(adjustedList::remove); cause.setStackTrace(adjustedList.toArray(new StackTraceElement[] {})); return cause; } private static class ReturnValueWrapper implements Callable { private final Object [MASK] ; private ReturnValueWrapper(Object [MASK] ) { this. [MASK] = [MASK] ; } @Override public Object call() { return [MASK] ; } } private static class SelfCallInfo extends ThreadLocal { Object replace(Object value) { Object current = get(); set(value); return current; } boolean checkSelfCall(Object value) { if (value == get()) { set(null); return false; } else { return true; } } } static class ConstructorShortcut implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { private final String identifier; ConstructorShortcut(String identifier) { this.identifier = identifier; } @Override public MethodVisitor wrap( TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, TypePool typePool, int writerFlags, int readerFlags) { if (instrumentedMethod.isConstructor() && !instrumentedType.represents(Object.class)) { MethodList constructors = instrumentedType .getSuperClass() .asErasure() .getDeclaredMethods() .filter(isConstructor().and(isVisibleTo(instrumentedType))); int arguments = Integer.MAX_VALUE; boolean packagePrivate = true; MethodDescription.InDefinedShape current = null; for (MethodDescription.InDefinedShape constructor : constructors) { // We are choosing the shortest constructor with regards to arguments. // Yet, we prefer a non-package-private constructor since they require // the super class to be on the same class loader. if (constructor.getParameters().size() < arguments && (packagePrivate || !constructor.isPackagePrivate())) { arguments = constructor.getParameters().size(); packagePrivate = constructor.isPackagePrivate(); current = constructor; } } if (current != null) { final MethodDescription.InDefinedShape selected = current; return new MethodVisitor(OpenedClassReader.ASM_API, methodVisitor) { @Override public void visitCode() { super.visitCode(); /* * The byte code that is added to the start of the method is roughly equivalent to * the following byte code for a hypothetical constructor of class Current: * * if (MockMethodDispatcher.isConstructorMock(, Current.class) { * super(); * Current o = (Current) MockMethodDispatcher.handleConstruction(Current.class, * this, * new Object[] {argument1, argument2, ...}, * new String[] {argumentType1, argumentType2, ...}); * if (o != null) { * this.field = o.field; // for each declared field * } * return; * } * * This avoids the invocation of the original constructor chain but fullfils the * verifier requirement to invoke a super constructor. */ Label label = new Label(); super.visitLdcInsn(identifier); if (implementationContext .getClassFileVersion() .isAtLeast(ClassFileVersion.JAVA_V5)) { super.visitLdcInsn(Type.getType(instrumentedType.getDescriptor())); } else { super.visitLdcInsn(instrumentedType.getName()); super.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName(Class.class), ""forName"", Type.getMethodDescriptor( Type.getType(Class.class), Type.getType(String.class)), false); } super.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName(MockMethodDispatcher.class), ""isConstructorMock"", Type.getMethodDescriptor( Type.BOOLEAN_TYPE, Type.getType(String.class), Type.getType(Class.class)), false); super.visitInsn(Opcodes.ICONST_0); super.visitJumpInsn(Opcodes.IF_ICMPEQ, label); super.visitVarInsn(Opcodes.ALOAD, 0); for (TypeDescription type : selected.getParameters().asTypeList().asErasures()) { if (type.represents(boolean.class) || type.represents(byte.class) || type.represents(short.class) || type.represents(char.class) || type.represents(int.class)) { super.visitInsn(Opcodes.ICONST_0); } else if (type.represents(long.class)) { super.visitInsn(Opcodes.LCONST_0); } else if (type.represents(float.class)) { super.visitInsn(Opcodes.FCONST_0); } else if (type.represents(double.class)) { super.visitInsn(Opcodes.DCONST_0); } else { super.visitInsn(Opcodes.ACONST_NULL); } } super.visitMethodInsn( Opcodes.INVOKESPECIAL, selected.getDeclaringType().getInternalName(), selected.getInternalName(), selected.getDescriptor(), false); super.visitLdcInsn(identifier); if (implementationContext .getClassFileVersion() .isAtLeast(ClassFileVersion.JAVA_V5)) { super.visitLdcInsn(Type.getType(instrumentedType.getDescriptor())); } else { super.visitLdcInsn(instrumentedType.getName()); super.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName(Class.class), ""forName"", Type.getMethodDescriptor( Type.getType(Class.class), Type.getType(String.class)), false); } super.visitVarInsn(Opcodes.ALOAD, 0); super.visitLdcInsn(instrumentedMethod.getParameters().size()); super.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName(Object.class)); int index = 0; for (ParameterDescription parameter : instrumentedMethod.getParameters()) { super.visitInsn(Opcodes.DUP); super.visitLdcInsn(index++); Type type = Type.getType( parameter.getType().asErasure().getDescriptor()); super.visitVarInsn( type.getOpcode(Opcodes.ILOAD), parameter.getOffset()); if (parameter.getType().isPrimitive()) { Type wrapper = Type.getType( parameter .getType() .asErasure() .asBoxed() .getDescriptor()); super.visitMethodInsn( Opcodes.INVOKESTATIC, wrapper.getInternalName(), ""valueOf"", Type.getMethodDescriptor(wrapper, type), false); } super.visitInsn(Opcodes.AASTORE); } index = 0; super.visitLdcInsn(instrumentedMethod.getParameters().size()); super.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName(String.class)); for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { super.visitInsn(Opcodes.DUP); super.visitLdcInsn(index++); super.visitLdcInsn(typeDescription.getName()); super.visitInsn(Opcodes.AASTORE); } super.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName(MockMethodDispatcher.class), ""handleConstruction"", Type.getMethodDescriptor( Type.getType(Object.class), Type.getType(String.class), Type.getType(Class.class), Type.getType(Object.class), Type.getType(Object[].class), Type.getType(String[].class)), false); FieldList fields = instrumentedType.getDeclaredFields().filter(not(isStatic())); super.visitTypeInsn( Opcodes.CHECKCAST, instrumentedType.getInternalName()); super.visitInsn(Opcodes.DUP); Label noSpy = new Label(); super.visitJumpInsn(Opcodes.IFNULL, noSpy); for (FieldDescription field : fields) { super.visitInsn(Opcodes.DUP); super.visitFieldInsn( Opcodes.GETFIELD, instrumentedType.getInternalName(), field.getInternalName(), field.getDescriptor()); super.visitVarInsn(Opcodes.ALOAD, 0); super.visitInsn( field.getType().getStackSize() == StackSize.DOUBLE ? Opcodes.DUP_X2 : Opcodes.DUP_X1); super.visitInsn(Opcodes.POP); super.visitFieldInsn( Opcodes.PUTFIELD, instrumentedType.getInternalName(), field.getInternalName(), field.getDescriptor()); } super.visitLabel(noSpy); if (implementationContext .getClassFileVersion() .isAtLeast(ClassFileVersion.JAVA_V6)) { Object[] locals = toFrames( instrumentedType.getInternalName(), instrumentedMethod .getParameters() .asTypeList() .asErasures()); super.visitFrame( Opcodes.F_FULL, locals.length, locals, 1, new Object[] {instrumentedType.getInternalName()}); } super.visitInsn(Opcodes.POP); super.visitInsn(Opcodes.RETURN); super.visitLabel(label); if (implementationContext .getClassFileVersion() .isAtLeast(ClassFileVersion.JAVA_V6)) { Object[] locals = toFrames( Opcodes.UNINITIALIZED_THIS, instrumentedMethod .getParameters() .asTypeList() .asErasures()); super.visitFrame( Opcodes.F_FULL, locals.length, locals, 0, new Object[0]); } } @Override public void visitMaxs(int maxStack, int maxLocals) { int prequel = Math.max(5, selected.getStackSize()); for (ParameterDescription parameter : instrumentedMethod.getParameters()) { prequel = Math.max( prequel, 6 + parameter.getType().getStackSize().getSize()); prequel = Math.max(prequel, 8); } super.visitMaxs(Math.max(maxStack, prequel), maxLocals); } }; } } return methodVisitor; } private static Object[] toFrames(Object self, List types) { Object[] frames = new Object[1 + types.size()]; frames[0] = self; int index = 0; for (TypeDescription type : types) { Object frame; if (type.represents(boolean.class) || type.represents(byte.class) || type.represents(short.class) || type.represents(char.class) || type.represents(int.class)) { frame = Opcodes.INTEGER; } else if (type.represents(long.class)) { frame = Opcodes.LONG; } else if (type.represents(float.class)) { frame = Opcodes.FLOAT; } else if (type.represents(double.class)) { frame = Opcodes.DOUBLE; } else { frame = type.getInternalName(); } frames[++index] = frame; } return frames; } } @Retention(RetentionPolicy.RUNTIME) @interface Identifier {} static class ForHashCode { @SuppressWarnings(""unused"") @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) private static boolean enter(@Identifier String id, @Advice.This Object self) { MockMethodDispatcher dispatcher = MockMethodDispatcher.get(id, self); return dispatcher != null && dispatcher.isMock(self); } @SuppressWarnings({""unused"", ""UnusedAssignment""}) @Advice.OnMethodExit private static void enter( @Advice.This Object self, @Advice.Return(readOnly = false) int hashCode, @Advice.Enter boolean skipped) { if (skipped) { hashCode = System.identityHashCode(self); } } } static class ForEquals { @SuppressWarnings(""unused"") @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) private static boolean enter(@Identifier String identifier, @Advice.This Object self) { MockMethodDispatcher dispatcher = MockMethodDispatcher.get(identifier, self); return dispatcher != null && dispatcher.isMock(self); } @SuppressWarnings({""unused"", ""UnusedAssignment""}) @Advice.OnMethodExit private static void enter( @Advice.This Object self, @Advice.Argument(0) Object other, @Advice.Return(readOnly = false) boolean equals, @Advice.Enter boolean skipped) { if (skipped) { equals = self == other; } } } static class ForStatic { @SuppressWarnings(""unused"") @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) private static Callable enter( @Identifier String identifier, @Advice.Origin Class type, @Advice.Origin Method origin, @Advice.AllArguments Object[] arguments) throws Throwable { MockMethodDispatcher dispatcher = MockMethodDispatcher.getStatic(identifier, type); if (dispatcher == null || !dispatcher.isMockedStatic(type)) { return null; } else { return dispatcher.handleStatic(type, origin, arguments); } } @SuppressWarnings({""unused"", ""UnusedAssignment""}) @Advice.OnMethodExit private static void exit( @Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object [MASK] , @Advice.Enter Callable mocked) throws Throwable { if (mocked != null) { [MASK] = mocked.call(); } } } public static class ForReadObject { @SuppressWarnings({""unused"", ""BanSerializableRead""}) public static void doReadObject( @Identifier String identifier, @This MockAccess thiz, @Argument(0) ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { objectInputStream.defaultReadObject(); MockMethodAdvice mockMethodAdvice = (MockMethodAdvice) MockMethodDispatcher.get(identifier, thiz); if (mockMethodAdvice != null) { mockMethodAdvice.interceptors.put(thiz, thiz.getMockitoInterceptor()); } } } } ","returned " "package com.blankj.utilcode.util; import android.annotation.SuppressLint; import android.os.Build; import android.os.Environment; import android.text.TextUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.util.Properties; /** *
     *     author: Blankj
     *     blog  : http://blankj.com
     *     time  : 2018/07/04
     *     desc  : utils about rom
     * 
    */ public final class RomUtils { private static final String[] ROM_HUAWEI = {""huawei""}; private static final String[] ROM_VIVO = {""vivo""}; private static final String[] ROM_XIAOMI = {""xiaomi""}; private static final String[] ROM_OPPO = {""oppo""}; private static final String[] ROM_LEECO = {""leeco"", ""letv""}; private static final String[] ROM_360 = {""360"", ""qiku""}; private static final String[] ROM_ZTE = {""zte""}; private static final String[] ROM_ONEPLUS = {""oneplus""}; private static final String[] ROM_NUBIA = {""nubia""}; private static final String[] ROM_COOLPAD = {""coolpad"", ""yulong""}; private static final String[] ROM_LG = {""lg"", ""lge""}; private static final String[] ROM_GOOGLE = {""google""}; private static final String[] ROM_SAMSUNG = {""samsung""}; private static final String[] ROM_MEIZU = {""meizu""}; private static final String[] ROM_LENOVO = {""lenovo""}; private static final String[] ROM_SMARTISAN = {""smartisan"", ""deltainno""}; private static final String[] ROM_HTC = {""htc""}; private static final String[] ROM_SONY = {""sony""}; private static final String[] ROM_GIONEE = {""gionee"", ""amigo""}; private static final String[] ROM_MOTOROLA = {""motorola""}; private static final String VERSION_PROPERTY_HUAWEI = ""ro.build.version.emui""; private static final String VERSION_PROPERTY_VIVO = ""ro.vivo.os.build. [MASK] .id""; private static final String VERSION_PROPERTY_XIAOMI = ""ro.build.version.incremental""; private static final String VERSION_PROPERTY_OPPO = ""ro.build.version.opporom""; private static final String VERSION_PROPERTY_LEECO = ""ro.letv.release.version""; private static final String VERSION_PROPERTY_360 = ""ro.build.uiversion""; private static final String VERSION_PROPERTY_ZTE = ""ro.build.MiFavor_version""; private static final String VERSION_PROPERTY_ONEPLUS = ""ro.rom.version""; private static final String VERSION_PROPERTY_NUBIA = ""ro.build.rom.id""; private final static String UNKNOWN = ""unknown""; private static RomInfo bean = null; private RomUtils() { throw new UnsupportedOperationException(""u can't instantiate me...""); } /** * Return whether the rom is made by huawei. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isHuawei() { return ROM_HUAWEI[0].equals(getRomInfo().name); } /** * Return whether the rom is made by vivo. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isVivo() { return ROM_VIVO[0].equals(getRomInfo().name); } /** * Return whether the rom is made by xiaomi. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isXiaomi() { return ROM_XIAOMI[0].equals(getRomInfo().name); } /** * Return whether the rom is made by oppo. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isOppo() { return ROM_OPPO[0].equals(getRomInfo().name); } /** * Return whether the rom is made by leeco. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isLeeco() { return ROM_LEECO[0].equals(getRomInfo().name); } /** * Return whether the rom is made by 360. * * @return {@code true}: yes
    {@code false}: no */ public static boolean is360() { return ROM_360[0].equals(getRomInfo().name); } /** * Return whether the rom is made by zte. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isZte() { return ROM_ZTE[0].equals(getRomInfo().name); } /** * Return whether the rom is made by oneplus. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isOneplus() { return ROM_ONEPLUS[0].equals(getRomInfo().name); } /** * Return whether the rom is made by nubia. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isNubia() { return ROM_NUBIA[0].equals(getRomInfo().name); } /** * Return whether the rom is made by coolpad. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isCoolpad() { return ROM_COOLPAD[0].equals(getRomInfo().name); } /** * Return whether the rom is made by lg. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isLg() { return ROM_LG[0].equals(getRomInfo().name); } /** * Return whether the rom is made by google. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isGoogle() { return ROM_GOOGLE[0].equals(getRomInfo().name); } /** * Return whether the rom is made by samsung. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isSamsung() { return ROM_SAMSUNG[0].equals(getRomInfo().name); } /** * Return whether the rom is made by meizu. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isMeizu() { return ROM_MEIZU[0].equals(getRomInfo().name); } /** * Return whether the rom is made by lenovo. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isLenovo() { return ROM_LENOVO[0].equals(getRomInfo().name); } /** * Return whether the rom is made by smartisan. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isSmartisan() { return ROM_SMARTISAN[0].equals(getRomInfo().name); } /** * Return whether the rom is made by htc. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isHtc() { return ROM_HTC[0].equals(getRomInfo().name); } /** * Return whether the rom is made by sony. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isSony() { return ROM_SONY[0].equals(getRomInfo().name); } /** * Return whether the rom is made by gionee. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isGionee() { return ROM_GIONEE[0].equals(getRomInfo().name); } /** * Return whether the rom is made by motorola. * * @return {@code true}: yes
    {@code false}: no */ public static boolean isMotorola() { return ROM_MOTOROLA[0].equals(getRomInfo().name); } /** * Return the rom's information. * * @return the rom's information */ public static RomInfo getRomInfo() { if (bean != null) return bean; bean = new RomInfo(); final String brand = getBrand(); final String manufacturer = getManufacturer(); if (isRightRom(brand, manufacturer, ROM_HUAWEI)) { bean.name = ROM_HUAWEI[0]; String version = getRomVersion(VERSION_PROPERTY_HUAWEI); String[] temp = version.split(""_""); if (temp.length > 1) { bean.version = temp[1]; } else { bean.version = version; } return bean; } if (isRightRom(brand, manufacturer, ROM_VIVO)) { bean.name = ROM_VIVO[0]; bean.version = getRomVersion(VERSION_PROPERTY_VIVO); return bean; } if (isRightRom(brand, manufacturer, ROM_XIAOMI)) { bean.name = ROM_XIAOMI[0]; bean.version = getRomVersion(VERSION_PROPERTY_XIAOMI); return bean; } if (isRightRom(brand, manufacturer, ROM_OPPO)) { bean.name = ROM_OPPO[0]; bean.version = getRomVersion(VERSION_PROPERTY_OPPO); return bean; } if (isRightRom(brand, manufacturer, ROM_LEECO)) { bean.name = ROM_LEECO[0]; bean.version = getRomVersion(VERSION_PROPERTY_LEECO); return bean; } if (isRightRom(brand, manufacturer, ROM_360)) { bean.name = ROM_360[0]; bean.version = getRomVersion(VERSION_PROPERTY_360); return bean; } if (isRightRom(brand, manufacturer, ROM_ZTE)) { bean.name = ROM_ZTE[0]; bean.version = getRomVersion(VERSION_PROPERTY_ZTE); return bean; } if (isRightRom(brand, manufacturer, ROM_ONEPLUS)) { bean.name = ROM_ONEPLUS[0]; bean.version = getRomVersion(VERSION_PROPERTY_ONEPLUS); return bean; } if (isRightRom(brand, manufacturer, ROM_NUBIA)) { bean.name = ROM_NUBIA[0]; bean.version = getRomVersion(VERSION_PROPERTY_NUBIA); return bean; } if (isRightRom(brand, manufacturer, ROM_COOLPAD)) { bean.name = ROM_COOLPAD[0]; } else if (isRightRom(brand, manufacturer, ROM_LG)) { bean.name = ROM_LG[0]; } else if (isRightRom(brand, manufacturer, ROM_GOOGLE)) { bean.name = ROM_GOOGLE[0]; } else if (isRightRom(brand, manufacturer, ROM_SAMSUNG)) { bean.name = ROM_SAMSUNG[0]; } else if (isRightRom(brand, manufacturer, ROM_MEIZU)) { bean.name = ROM_MEIZU[0]; } else if (isRightRom(brand, manufacturer, ROM_LENOVO)) { bean.name = ROM_LENOVO[0]; } else if (isRightRom(brand, manufacturer, ROM_SMARTISAN)) { bean.name = ROM_SMARTISAN[0]; } else if (isRightRom(brand, manufacturer, ROM_HTC)) { bean.name = ROM_HTC[0]; } else if (isRightRom(brand, manufacturer, ROM_SONY)) { bean.name = ROM_SONY[0]; } else if (isRightRom(brand, manufacturer, ROM_GIONEE)) { bean.name = ROM_GIONEE[0]; } else if (isRightRom(brand, manufacturer, ROM_MOTOROLA)) { bean.name = ROM_MOTOROLA[0]; } else { bean.name = manufacturer; } bean.version = getRomVersion(""""); return bean; } private static boolean isRightRom(final String brand, final String manufacturer, final String... names) { for (String name : names) { if (brand.contains(name) || manufacturer.contains(name)) { return true; } } return false; } private static String getManufacturer() { try { String manufacturer = Build.MANUFACTURER; if (!TextUtils.isEmpty(manufacturer)) { return manufacturer.toLowerCase(); } } catch (Throwable ignore) {/**/} return UNKNOWN; } private static String getBrand() { try { String brand = Build.BRAND; if (!TextUtils.isEmpty(brand)) { return brand.toLowerCase(); } } catch (Throwable ignore) {/**/} return UNKNOWN; } private static String getRomVersion(final String propertyName) { String ret = """"; if (!TextUtils.isEmpty(propertyName)) { ret = getSystemProperty(propertyName); } if (TextUtils.isEmpty(ret) || ret.equals(UNKNOWN)) { try { String [MASK] = Build.DISPLAY; if (!TextUtils.isEmpty( [MASK] )) { ret = [MASK] .toLowerCase(); } } catch (Throwable ignore) {/**/} } if (TextUtils.isEmpty(ret)) { return UNKNOWN; } return ret; } private static String getSystemProperty(final String name) { String prop = getSystemPropertyByShell(name); if (!TextUtils.isEmpty(prop)) return prop; prop = getSystemPropertyByStream(name); if (!TextUtils.isEmpty(prop)) return prop; if (Build.VERSION.SDK_INT < 28) { return getSystemPropertyByReflect(name); } return prop; } private static String getSystemPropertyByShell(final String propName) { String line; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec(""getprop "" + propName); input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); String ret = input.readLine(); if (ret != null) { return ret; } } catch (IOException ignore) { } finally { if (input != null) { try { input.close(); } catch (IOException ignore) {/**/} } } return """"; } private static String getSystemPropertyByStream(final String key) { try { Properties prop = new Properties(); FileInputStream is = new FileInputStream( new File(Environment.getRootDirectory(), ""build.prop"") ); prop.load(is); return prop.getProperty(key, """"); } catch (Exception ignore) {/**/} return """"; } private static String getSystemPropertyByReflect(String key) { try { @SuppressLint(""PrivateApi"") Class clz = Class.forName(""android.os.SystemProperties""); Method getMethod = clz.getMethod(""get"", String.class, String.class); return (String) getMethod.invoke(clz, key, """"); } catch (Exception e) {/**/} return """"; } public static class RomInfo { private String name; private String version; public String getName() { return name; } public String getVersion() { return version; } @Override public String toString() { return ""RomInfo{name="" + name + "", version="" + version + ""}""; } } }","display " "/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the ""License""); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an ""AS IS"" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.maps.tiled; import com.badlogic.gdx.assets.AssetDescriptor; import com.badlogic.gdx.assets.AssetLoaderParameters; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.assets.loaders.FileHandleResolver; import com.badlogic.gdx.assets.loaders.SynchronousAssetLoader; import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.maps.ImageResolver; import com.badlogic.gdx.maps.ImageResolver.AssetManagerImageResolver; import com.badlogic.gdx.maps.ImageResolver.DirectImageResolver; import com.badlogic.gdx.maps.MapProperties; import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell; import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile; import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.XmlReader; import com.badlogic.gdx.utils.XmlReader.Element; import java.io.IOException; import java.util.StringTokenizer; public class TideMapLoader extends SynchronousAssetLoader { public static class Parameters extends AssetLoaderParameters { } private XmlReader xml = new XmlReader(); private Element root; public TideMapLoader () { super(new InternalFileHandleResolver()); } public TideMapLoader (FileHandleResolver resolver) { super(resolver); } public TiledMap load (String fileName) { try { FileHandle tideFile = resolve(fileName); root = xml.parse(tideFile); ObjectMap textures = new ObjectMap(); for (FileHandle textureFile : loadTileSheets(root, tideFile)) { textures.put(textureFile.path(), new Texture(textureFile)); } DirectImageResolver imageResolver = new DirectImageResolver(textures); TiledMap map = loadMap(root, tideFile, imageResolver); map.setOwnedResources(textures.values().toArray()); return map; } catch (IOException e) { throw new GdxRuntimeException(""Couldn't load tilemap '"" + fileName + ""'"", e); } } @Override public TiledMap load (AssetManager assetManager, String fileName, FileHandle tideFile, Parameters parameter) { try { return loadMap(root, tideFile, new AssetManagerImageResolver(assetManager)); } catch (Exception e) { throw new GdxRuntimeException(""Couldn't load tilemap '"" + fileName + ""'"", e); } } @Override public Array getDependencies (String fileName, FileHandle tmxFile, Parameters parameter) { Array dependencies = new Array(); try { root = xml.parse(tmxFile); for (FileHandle image : loadTileSheets(root, tmxFile)) { dependencies.add(new AssetDescriptor(image.path(), Texture.class)); } return dependencies; } catch (IOException e) { throw new GdxRuntimeException(""Couldn't load tilemap '"" + fileName + ""'"", e); } } /** Loads the map data, given the XML root element and an {@link ImageResolver} used to return the tileset Textures * @param root the XML root element * @param tmxFile the Filehandle of the tmx file * @param imageResolver the {@link ImageResolver} * @return the {@link TiledMap} */ private TiledMap loadMap (Element root, FileHandle tmxFile, ImageResolver imageResolver) { TiledMap map = new TiledMap(); Element properties = root.getChildByName(""Properties""); if (properties != null) { loadProperties(map.getProperties(), properties); } Element tilesheets = root.getChildByName(""TileSheets""); for (Element tilesheet : tilesheets.getChildrenByName(""TileSheet"")) { loadTileSheet(map, tilesheet, tmxFile, imageResolver); } Element layers = root.getChildByName(""Layers""); for (Element layer : layers.getChildrenByName(""Layer"")) { loadLayer(map, layer); } return map; } /** Loads the tilesets * @param root the root XML element * @return a list of filenames for images containing tiles * @throws IOException */ private Array loadTileSheets (Element root, FileHandle tideFile) throws IOException { Array images = new Array(); Element tilesheets = root.getChildByName(""TileSheets""); for (Element tileset : tilesheets.getChildrenByName(""TileSheet"")) { Element imageSource = tileset.getChildByName(""ImageSource""); FileHandle image = getRelativeFileHandle(tideFile, imageSource.getText()); images.add(image); } return images; } private void loadTileSheet (TiledMap map, Element element, FileHandle tideFile, ImageResolver imageResolver) { if (element.getName().equals(""TileSheet"")) { String id = element.getAttribute(""Id""); String description = element.getChildByName(""Description"").getText(); String imageSource = element.getChildByName(""ImageSource"").getText(); Element [MASK] = element.getChildByName(""Alignment""); String sheetSize = [MASK] .getAttribute(""SheetSize""); String tileSize = [MASK] .getAttribute(""TileSize""); String margin = [MASK] .getAttribute(""Margin""); String spacing = [MASK] .getAttribute(""Spacing""); String[] sheetSizeParts = sheetSize.split("" x ""); int sheetSizeX = Integer.parseInt(sheetSizeParts[0]); int sheetSizeY = Integer.parseInt(sheetSizeParts[1]); String[] tileSizeParts = tileSize.split("" x ""); int tileSizeX = Integer.parseInt(tileSizeParts[0]); int tileSizeY = Integer.parseInt(tileSizeParts[1]); String[] marginParts = margin.split("" x ""); int marginX = Integer.parseInt(marginParts[0]); int marginY = Integer.parseInt(marginParts[1]); String[] spacingParts = margin.split("" x ""); int spacingX = Integer.parseInt(spacingParts[0]); int spacingY = Integer.parseInt(spacingParts[1]); FileHandle image = getRelativeFileHandle(tideFile, imageSource); TextureRegion texture = imageResolver.getImage(image.path()); TiledMapTileSets tilesets = map.getTileSets(); int firstgid = 1; for (TiledMapTileSet tileset : tilesets) { firstgid += tileset.size(); } TiledMapTileSet tileset = new TiledMapTileSet(); tileset.setName(id); tileset.getProperties().put(""firstgid"", firstgid); int gid = firstgid; int stopWidth = texture.getRegionWidth() - tileSizeX; int stopHeight = texture.getRegionHeight() - tileSizeY; for (int y = marginY; y <= stopHeight; y += tileSizeY + spacingY) { for (int x = marginX; x <= stopWidth; x += tileSizeX + spacingX) { TiledMapTile tile = new StaticTiledMapTile(new TextureRegion(texture, x, y, tileSizeX, tileSizeY)); tile.setId(gid); tileset.putTile(gid++, tile); } } Element properties = element.getChildByName(""Properties""); if (properties != null) { loadProperties(tileset.getProperties(), properties); } tilesets.addTileSet(tileset); } } private void loadLayer (TiledMap map, Element element) { if (element.getName().equals(""Layer"")) { String id = element.getAttribute(""Id""); String visible = element.getAttribute(""Visible""); Element dimensions = element.getChildByName(""Dimensions""); String layerSize = dimensions.getAttribute(""LayerSize""); String tileSize = dimensions.getAttribute(""TileSize""); String[] layerSizeParts = layerSize.split("" x ""); int layerSizeX = Integer.parseInt(layerSizeParts[0]); int layerSizeY = Integer.parseInt(layerSizeParts[1]); String[] tileSizeParts = tileSize.split("" x ""); int tileSizeX = Integer.parseInt(tileSizeParts[0]); int tileSizeY = Integer.parseInt(tileSizeParts[1]); TiledMapTileLayer layer = new TiledMapTileLayer(layerSizeX, layerSizeY, tileSizeX, tileSizeY); layer.setName(id); layer.setVisible(visible.equalsIgnoreCase(""True"")); Element tileArray = element.getChildByName(""TileArray""); Array rows = tileArray.getChildrenByName(""Row""); TiledMapTileSets tilesets = map.getTileSets(); TiledMapTileSet currentTileSet = null; int firstgid = 0; int x, y; for (int row = 0, rowCount = rows.size; row < rowCount; row++) { Element currentRow = rows.get(row); y = rowCount - 1 - row; x = 0; for (int child = 0, childCount = currentRow.getChildCount(); child < childCount; child++) { Element currentChild = currentRow.getChild(child); String name = currentChild.getName(); if (name.equals(""TileSheet"")) { currentTileSet = tilesets.getTileSet(currentChild.getAttribute(""Ref"")); firstgid = currentTileSet.getProperties().get(""firstgid"", Integer.class); } else if (name.equals(""Null"")) { x += currentChild.getIntAttribute(""Count""); } else if (name.equals(""Static"")) { Cell cell = new Cell(); cell.setTile(currentTileSet.getTile(firstgid + currentChild.getIntAttribute(""Index""))); layer.setCell(x++, y, cell); } else if (name.equals(""Animated"")) { // Create an AnimatedTile int interval = currentChild.getInt(""Interval""); Element frames = currentChild.getChildByName(""Frames""); Array frameTiles = new Array(); for (int frameChild = 0, frameChildCount = frames.getChildCount(); frameChild < frameChildCount; frameChild++) { Element frame = frames.getChild(frameChild); String frameName = frame.getName(); if (frameName.equals(""TileSheet"")) { currentTileSet = tilesets.getTileSet(frame.getAttribute(""Ref"")); firstgid = currentTileSet.getProperties().get(""firstgid"", Integer.class); } else if (frameName.equals(""Static"")) { frameTiles.add((StaticTiledMapTile)currentTileSet.getTile(firstgid + frame.getIntAttribute(""Index""))); } } Cell cell = new Cell(); cell.setTile(new AnimatedTiledMapTile(interval / 1000f, frameTiles)); layer.setCell(x++, y, cell); // TODO: Reuse existing animated tiles } } } Element properties = element.getChildByName(""Properties""); if (properties != null) { loadProperties(layer.getProperties(), properties); } map.getLayers().add(layer); } } private void loadProperties (MapProperties properties, Element element) { if (element.getName().equals(""Properties"")) { for (Element property : element.getChildrenByName(""Property"")) { String key = property.getAttribute(""Key"", null); String type = property.getAttribute(""Type"", null); String value = property.getText(); if (type.equals(""Int32"")) { properties.put(key, Integer.parseInt(value)); } else if (type.equals(""String"")) { properties.put(key, value); } else if (type.equals(""Boolean"")) { properties.put(key, value.equalsIgnoreCase(""true"")); } else { properties.put(key, value); } } } } private static FileHandle getRelativeFileHandle (FileHandle file, String path) { StringTokenizer tokenizer = new StringTokenizer(path, ""\\/""); FileHandle result = file.parent(); while (tokenizer.hasMoreElements()) { String token = tokenizer.nextToken(); if (token.equals("".."")) result = result.parent(); else { result = result.child(token); } } return result; } } ","alignment " "/******************************************************************************* * Copyright (C) 2018, OpenRefine contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ""AS IS"" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ package com.google.refine.expr; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import com.google.refine.RefineTest; import com.google.refine.util.TestUtils; public class EvalErrorTests extends RefineTest { @Override @BeforeTest public void init() { logger = LoggerFactory.getLogger(this.getClass()); } @Test public void serializeEvalError() { EvalError e = new EvalError(""This is a critical error""); TestUtils.isSerializedTo(e, ""{\""type\"":\""error\"",\""message\"":\""This is a critical error\""}""); } @Test public void testInnerHtml() { Assert.assertTrue(invoke(""innerHtml"") instanceof EvalError); Assert.assertTrue(invoke(""innerHtml"", ""test"") instanceof EvalError); EvalError [MASK] = (EvalError) invoke(""innerHtml"", ""test""); Assert.assertEquals( [MASK] .toString(), ""innerHtml() cannot work with this \'string\'. The first parameter is not an HTML Element. Please first use parseHtml(string) and select(query) prior to using this function""); } @Test public void testWholeText() { Assert.assertTrue(invoke(""wholeText"") instanceof EvalError); Assert.assertTrue(invoke(""wholeText"", ""test"") instanceof EvalError); EvalError [MASK] = (EvalError) invoke(""wholeText"", ""test""); Assert.assertEquals( [MASK] .toString(), ""wholeText() cannot work with this \'string\' and failed as the first parameter is not an XML or HTML Element. Please first use parseXml() or parseHtml() and select(query) prior to using this function""); } @Test public void testXmlText() { Assert.assertTrue(invoke(""xmlText"") instanceof EvalError); Assert.assertTrue(invoke(""xmlText"", ""test"") instanceof EvalError); EvalError [MASK] = (EvalError) invoke(""xmlText"", ""test""); Assert.assertEquals( [MASK] .toString(), ""xmlText() cannot work with this \'string\' and failed as the first parameter is not an XML or HTML Element. Please first use parseXml() or parseHtml() and select(query) prior to using this function""); } } ","evalError " "/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.drawee.generic; import android.content.res.Resources; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Animatable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import androidx.annotation.VisibleForTesting; import com.facebook.common.internal.Preconditions; import com.facebook.drawee.drawable.DrawableParent; import com.facebook.drawee.drawable.FadeDrawable; import com.facebook.drawee.drawable.ForwardingDrawable; import com.facebook.drawee.drawable.MatrixDrawable; import com.facebook.drawee.drawable.ScaleTypeDrawable; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.interfaces.SettableDraweeHierarchy; import com.facebook.fresco.ui.common.OnFadeListener; import com.facebook.imagepipeline.systrace.FrescoSystrace; import javax.annotation.Nullable; /** * A SettableDraweeHierarchy that displays placeholder image until the actual image is set. If * provided, failure image will be used in case of failure (placeholder otherwise). If provided, * retry image will be used in case of failure when retrying is enabled. If provided, progressbar * will be displayed until fully loaded. Each image can be displayed with a different scale type (or * no scaling at all). Fading between the layers is supported. Rounding is supported. * *

    Example hierarchy with a placeholder, retry, failure and the actual image: * *

     *  o RootDrawable (top level drawable)
     *  |
     *  +--o FadeDrawable
     *     |
     *     +--o ScaleTypeDrawable (placeholder branch, optional)
     *     |  |
     *     |  +--o Drawable (placeholder image)
     *     |
     *     +--o ScaleTypeDrawable (actual image branch)
     *     |  |
     *     |  +--o ForwardingDrawable (actual image wrapper)
     *     |     |
     *     |     +--o Drawable (actual image)
     *     |
     *     +--o null (progress bar branch, optional)
     *     |
     *     +--o Drawable (retry image branch, optional)
     *     |
     *     +--o ScaleTypeDrawable (failure image branch, optional)
     *        |
     *        +--o Drawable (failure image)
     *  
    * *

    Note: * *

      *
    • RootDrawable and FadeDrawable are always created. *
    • All branches except the actual image branch are optional (placeholder, failure, retry, * progress bar). If some branch is not specified it won't be created. Index in FadeDrawable * will still be reserved though. *
    • If overlays and/or background are specified, they are added to the same fade drawable, and * are always being displayed. *
    • ScaleType and Matrix transformations will be added only if specified. If both are * unspecified, then the branch for that image is attached to FadeDrawable directly. Matrix * transformation is only supported for the actual image, and it is not recommended to be * used. *
    • Rounding, if specified, is applied to all layers. Rounded drawable can either wrap * FadeDrawable, or if leaf rounding is specified, each leaf drawable will be rounded * separately. *
    • A particular drawable instance should be used by only one DH. If more than one DH is being * built with the same builder, different drawable instances must be specified for each DH. *
    */ public class GenericDraweeHierarchy implements SettableDraweeHierarchy { private static final int BACKGROUND_IMAGE_INDEX = 0; private static final int PLACEHOLDER_IMAGE_INDEX = 1; private static final int ACTUAL_IMAGE_INDEX = 2; private static final int PROGRESS_BAR_IMAGE_INDEX = 3; private static final int RETRY_IMAGE_INDEX = 4; private static final int FAILURE_IMAGE_INDEX = 5; private static final int OVERLAY_IMAGES_INDEX = 6; private final Drawable mEmptyActualImageDrawable = new ColorDrawable(Color.TRANSPARENT); private final Resources mResources; private @Nullable RoundingParams mRoundingParams; private final RootDrawable mTopLevelDrawable; private final FadeDrawable mFadeDrawable; private final ForwardingDrawable mActualImageWrapper; GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) { if (FrescoSystrace.isTracing()) { FrescoSystrace.beginSection(""GenericDraweeHierarchy()""); } mResources = builder.getResources(); mRoundingParams = builder.getRoundingParams(); mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable); int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 1; // make sure there is at least one overlay to make setOverlayImage(Drawable) // method work. if (numOverlays == 0) { numOverlays = 1; } numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0; // layer indices and count int numLayers = OVERLAY_IMAGES_INDEX + numOverlays; // array of layers Drawable[] layers = new Drawable[numLayers]; layers[BACKGROUND_IMAGE_INDEX] = buildBranch(builder.getBackground(), null); layers[PLACEHOLDER_IMAGE_INDEX] = buildBranch(builder.getPlaceholderImage(), builder.getPlaceholderImageScaleType()); layers[ACTUAL_IMAGE_INDEX] = buildActualImageBranch( mActualImageWrapper, builder.getActualImageScaleType(), builder.getActualImageFocusPoint(), builder.getActualImageColorFilter()); layers[PROGRESS_BAR_IMAGE_INDEX] = buildBranch(builder.getProgressBarImage(), builder.getProgressBarImageScaleType()); layers[RETRY_IMAGE_INDEX] = buildBranch(builder.getRetryImage(), builder.getRetryImageScaleType()); layers[FAILURE_IMAGE_INDEX] = buildBranch(builder.getFailureImage(), builder.getFailureImageScaleType()); if (numOverlays > 0) { int index = 0; if (builder.getOverlays() != null) { for (Drawable overlay : builder.getOverlays()) { layers[OVERLAY_IMAGES_INDEX + index++] = buildBranch(overlay, null); } } else { index = 1; // reserve space for one overlay } if (builder.getPressedStateOverlay() != null) { layers[OVERLAY_IMAGES_INDEX + index] = buildBranch(builder.getPressedStateOverlay(), null); } } // fade drawable composed of layers mFadeDrawable = new FadeDrawable(layers, false, ACTUAL_IMAGE_INDEX); mFadeDrawable.setTransitionDuration(builder.getFadeDuration()); // rounded corners drawable (optional) Drawable maybeRoundedDrawable = WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams); // top-level drawable mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable); mTopLevelDrawable.mutate(); resetFade(); if (FrescoSystrace.isTracing()) { FrescoSystrace.endSection(); } } @Nullable private Drawable buildActualImageBranch( Drawable drawable, @Nullable ScalingUtils.ScaleType scaleType, @Nullable PointF [MASK] , @Nullable ColorFilter colorFilter) { drawable.setColorFilter(colorFilter); drawable = WrappingUtils.maybeWrapWithScaleType(drawable, scaleType, [MASK] ); return drawable; } /** Applies scale type and rounding (both if specified). */ @Nullable private Drawable buildBranch( @Nullable Drawable drawable, @Nullable ScalingUtils.ScaleType scaleType) { drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources); drawable = WrappingUtils.maybeWrapWithScaleType(drawable, scaleType); return drawable; } private void resetActualImages() { mActualImageWrapper.setDrawable(mEmptyActualImageDrawable); } private void resetFade() { if (mFadeDrawable != null) { mFadeDrawable.beginBatchMode(); // turn on all layers (backgrounds, branches, overlays) mFadeDrawable.fadeInAllLayers(); // turn off branches (leaving backgrounds and overlays on) fadeOutBranches(); // turn on placeholder fadeInLayer(PLACEHOLDER_IMAGE_INDEX); mFadeDrawable.finishTransitionImmediately(); mFadeDrawable.endBatchMode(); } } private void fadeOutBranches() { fadeOutLayer(PLACEHOLDER_IMAGE_INDEX); fadeOutLayer(ACTUAL_IMAGE_INDEX); fadeOutLayer(PROGRESS_BAR_IMAGE_INDEX); fadeOutLayer(RETRY_IMAGE_INDEX); fadeOutLayer(FAILURE_IMAGE_INDEX); } private void fadeInLayer(int index) { if (index >= 0) { mFadeDrawable.fadeInLayer(index); } } private void fadeOutLayer(int index) { if (index >= 0) { mFadeDrawable.fadeOutLayer(index); } } private void setProgress(float progress) { Drawable progressBarDrawable = mFadeDrawable.getDrawable(PROGRESS_BAR_IMAGE_INDEX); if (progressBarDrawable == null) { return; } // display progressbar when not fully loaded, hide otherwise if (progress >= 0.999f) { if (progressBarDrawable instanceof Animatable) { ((Animatable) progressBarDrawable).stop(); } fadeOutLayer(PROGRESS_BAR_IMAGE_INDEX); } else { if (progressBarDrawable instanceof Animatable) { ((Animatable) progressBarDrawable).start(); } fadeInLayer(PROGRESS_BAR_IMAGE_INDEX); } // set drawable level, scaled to [0, 10000] per drawable specification progressBarDrawable.setLevel(Math.round(progress * 10000)); } // SettableDraweeHierarchy interface @Override public Drawable getTopLevelDrawable() { return mTopLevelDrawable; } @Override public void reset() { resetActualImages(); resetFade(); } @Override public void setImage(Drawable drawable, float progress, boolean immediate) { drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources); drawable.mutate(); mActualImageWrapper.setDrawable(drawable); mFadeDrawable.beginBatchMode(); fadeOutBranches(); fadeInLayer(ACTUAL_IMAGE_INDEX); setProgress(progress); if (immediate) { mFadeDrawable.finishTransitionImmediately(); } mFadeDrawable.endBatchMode(); } @Override public void setProgress(float progress, boolean immediate) { if (mFadeDrawable.getDrawable(PROGRESS_BAR_IMAGE_INDEX) == null) { return; } mFadeDrawable.beginBatchMode(); setProgress(progress); if (immediate) { mFadeDrawable.finishTransitionImmediately(); } mFadeDrawable.endBatchMode(); } @Override public void setFailure(Throwable throwable) { mFadeDrawable.beginBatchMode(); fadeOutBranches(); if (mFadeDrawable.getDrawable(FAILURE_IMAGE_INDEX) != null) { fadeInLayer(FAILURE_IMAGE_INDEX); } else { fadeInLayer(PLACEHOLDER_IMAGE_INDEX); } mFadeDrawable.endBatchMode(); } @Override public void setRetry(Throwable throwable) { mFadeDrawable.beginBatchMode(); fadeOutBranches(); if (mFadeDrawable.getDrawable(RETRY_IMAGE_INDEX) != null) { fadeInLayer(RETRY_IMAGE_INDEX); } else { fadeInLayer(PLACEHOLDER_IMAGE_INDEX); } mFadeDrawable.endBatchMode(); } @Override public void setControllerOverlay(@Nullable Drawable drawable) { mTopLevelDrawable.setControllerOverlay(drawable); } @Override public Rect getBounds() { return mTopLevelDrawable.getBounds(); } // Helper methods for accessing layers /** * Gets the lowest parent drawable for the layer at the specified index. * *

    Following drawables are considered as parents: FadeDrawable, MatrixDrawable, * ScaleTypeDrawable. This is because those drawables are added automatically by the hierarchy (if * specified), whereas their children are created externally by the client code. When we need to * change the previously set drawable this is the parent whose child needs to be replaced. */ private DrawableParent getParentDrawableAtIndex(int index) { DrawableParent parent = mFadeDrawable.getDrawableParentForIndex(index); if (parent.getDrawable() instanceof MatrixDrawable) { parent = (MatrixDrawable) parent.getDrawable(); } if (parent.getDrawable() instanceof ScaleTypeDrawable) { parent = (ScaleTypeDrawable) parent.getDrawable(); } return parent; } /** * Sets the drawable at the specified index while keeping the old scale type and rounding. In case * the given drawable is null, scale type gets cleared too. */ private void setChildDrawableAtIndex(int index, @Nullable Drawable drawable) { if (drawable == null) { mFadeDrawable.setDrawable(index, null); return; } drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources); getParentDrawableAtIndex(index).setDrawable(drawable); } /** * Gets the ScaleTypeDrawable at the specified index. In case there is no child at the specified * index, a NullPointerException is thrown. In case there is a child, but the ScaleTypeDrawable * does not exist, the child will be wrapped with a new ScaleTypeDrawable. */ private ScaleTypeDrawable getScaleTypeDrawableAtIndex(int index) { DrawableParent parent = getParentDrawableAtIndex(index); if (parent instanceof ScaleTypeDrawable) { return (ScaleTypeDrawable) parent; } else { return WrappingUtils.wrapChildWithScaleType(parent, ScalingUtils.ScaleType.FIT_XY); } } /** Returns whether the given layer has a scale type drawable. */ private boolean hasScaleTypeDrawableAtIndex(int index) { DrawableParent parent = getParentDrawableAtIndex(index); return (parent instanceof ScaleTypeDrawable); } // Mutability /** Sets the fade duration. */ public void setFadeDuration(int durationMs) { mFadeDrawable.setTransitionDuration(durationMs); } /** Gets the fade duration. */ public int getFadeDuration() { return mFadeDrawable.getTransitionDuration(); } /** Sets the actual image focus point. */ public void setActualImageFocusPoint(PointF [MASK] ) { Preconditions.checkNotNull( [MASK] ); getScaleTypeDrawableAtIndex(ACTUAL_IMAGE_INDEX).setFocusPoint( [MASK] ); } /** Sets the actual image scale type. */ public void setActualImageScaleType(ScalingUtils.ScaleType scaleType) { Preconditions.checkNotNull(scaleType); getScaleTypeDrawableAtIndex(ACTUAL_IMAGE_INDEX).setScaleType(scaleType); } public @Nullable ScalingUtils.ScaleType getActualImageScaleType() { if (!hasScaleTypeDrawableAtIndex(ACTUAL_IMAGE_INDEX)) { return null; } return getScaleTypeDrawableAtIndex(ACTUAL_IMAGE_INDEX).getScaleType(); } public @Nullable PointF getActualImageFocusPoint() { if (!hasScaleTypeDrawableAtIndex(ACTUAL_IMAGE_INDEX)) { return null; } return getScaleTypeDrawableAtIndex(ACTUAL_IMAGE_INDEX).getFocusPoint(); } /** Sets the color filter to be applied on the actual image. */ public void setActualImageColorFilter(@Nullable ColorFilter colorfilter) { mActualImageWrapper.setColorFilter(colorfilter); } /** Gets the non-cropped post-scaling bounds of the actual image. */ public void getActualImageBounds(RectF outBounds) { mActualImageWrapper.getTransformedBounds(outBounds); } /** Sets a new placeholder drawable with old scale type. */ public void setPlaceholderImage(@Nullable Drawable drawable) { setChildDrawableAtIndex(PLACEHOLDER_IMAGE_INDEX, drawable); } /** Sets a new placeholder drawable with scale type. */ public void setPlaceholderImage(Drawable drawable, ScalingUtils.ScaleType scaleType) { setChildDrawableAtIndex(PLACEHOLDER_IMAGE_INDEX, drawable); getScaleTypeDrawableAtIndex(PLACEHOLDER_IMAGE_INDEX).setScaleType(scaleType); } /** @return true if there is a placeholder image set. */ public boolean hasPlaceholderImage() { return mFadeDrawable.getDrawable(PLACEHOLDER_IMAGE_INDEX) != null; } /** Sets the placeholder image focus point. */ public void setPlaceholderImageFocusPoint(PointF [MASK] ) { Preconditions.checkNotNull( [MASK] ); getScaleTypeDrawableAtIndex(PLACEHOLDER_IMAGE_INDEX).setFocusPoint( [MASK] ); } /** * Sets a new placeholder drawable with old scale type. * * @param resourceId an identifier of an Android drawable or color resource. */ public void setPlaceholderImage(int resourceId) { setPlaceholderImage(mResources.getDrawable(resourceId)); } /** * Sets a new placeholder drawable with scale type. * * @param resourceId an identifier of an Android drawable or color resource. * @param ScalingUtils.ScaleType a new scale type. */ public void setPlaceholderImage(int resourceId, ScalingUtils.ScaleType scaleType) { setPlaceholderImage(mResources.getDrawable(resourceId), scaleType); } /** Sets a new failure drawable with old scale type. */ public void setFailureImage(@Nullable Drawable drawable) { setChildDrawableAtIndex(FAILURE_IMAGE_INDEX, drawable); } /** Sets a new failure drawable with scale type. */ public void setFailureImage(Drawable drawable, ScalingUtils.ScaleType scaleType) { setChildDrawableAtIndex(FAILURE_IMAGE_INDEX, drawable); getScaleTypeDrawableAtIndex(FAILURE_IMAGE_INDEX).setScaleType(scaleType); } /** * Sets a new failure drawable with old scale type. * * @param resourceId an identifier of an Android drawable or color resource. */ public void setFailureImage(int resourceId) { setFailureImage(mResources.getDrawable(resourceId)); } /** * Sets a new failure drawable with scale type. * * @param resourceId an identifier of an Android drawable or color resource. * @param ScalingUtils.ScaleType a new scale type. */ public void setFailureImage(int resourceId, ScalingUtils.ScaleType scaleType) { setFailureImage(mResources.getDrawable(resourceId), scaleType); } /** Sets a new retry drawable with old scale type. */ public void setRetryImage(@Nullable Drawable drawable) { setChildDrawableAtIndex(RETRY_IMAGE_INDEX, drawable); } /** Sets a new retry drawable with scale type. */ public void setRetryImage(Drawable drawable, ScalingUtils.ScaleType scaleType) { setChildDrawableAtIndex(RETRY_IMAGE_INDEX, drawable); getScaleTypeDrawableAtIndex(RETRY_IMAGE_INDEX).setScaleType(scaleType); } /** * Sets a new retry drawable with old scale type. * * @param resourceId an identifier of an Android drawable or color resource. */ public void setRetryImage(int resourceId) { setRetryImage(mResources.getDrawable(resourceId)); } /** * Sets a new retry drawable with scale type. * * @param resourceId an identifier of an Android drawable or color resource. * @param ScalingUtils.ScaleType a new scale type. */ public void setRetryImage(int resourceId, ScalingUtils.ScaleType scaleType) { setRetryImage(mResources.getDrawable(resourceId), scaleType); } /** Sets a new progress bar drawable with old scale type. */ public void setProgressBarImage(@Nullable Drawable drawable) { setChildDrawableAtIndex(PROGRESS_BAR_IMAGE_INDEX, drawable); } /** Sets a new progress bar drawable with scale type. */ public void setProgressBarImage(Drawable drawable, ScalingUtils.ScaleType scaleType) { setChildDrawableAtIndex(PROGRESS_BAR_IMAGE_INDEX, drawable); getScaleTypeDrawableAtIndex(PROGRESS_BAR_IMAGE_INDEX).setScaleType(scaleType); } /** * Sets a new progress bar drawable with old scale type. * * @param resourceId an identifier of an Android drawable or color resource. */ public void setProgressBarImage(int resourceId) { setProgressBarImage(mResources.getDrawable(resourceId)); } /** * Sets a new progress bar drawable with scale type. * * @param resourceId an identifier of an Android drawable or color resource. * @param ScalingUtils.ScaleType a new scale type. */ public void setProgressBarImage(int resourceId, ScalingUtils.ScaleType scaleType) { setProgressBarImage(mResources.getDrawable(resourceId), scaleType); } /** Sets the background image if allowed. */ public void setBackgroundImage(@Nullable Drawable drawable) { setChildDrawableAtIndex(BACKGROUND_IMAGE_INDEX, drawable); } /** * Sets a new overlay image at the specified index. * *

    This method will throw if the given index is out of bounds. * * @param drawable background image */ public void setOverlayImage(int index, @Nullable Drawable drawable) { // Note that overlays are by definition top-most and therefore the last elements in the array. Preconditions.checkArgument( index >= 0 && OVERLAY_IMAGES_INDEX + index < mFadeDrawable.getNumberOfLayers(), ""The given index does not correspond to an overlay image.""); setChildDrawableAtIndex(OVERLAY_IMAGES_INDEX + index, drawable); } /** Sets the overlay image if allowed. */ public void setOverlayImage(@Nullable Drawable drawable) { setOverlayImage(0, drawable); } /** Sets the rounding params. */ public void setRoundingParams(@Nullable RoundingParams roundingParams) { mRoundingParams = roundingParams; WrappingUtils.updateOverlayColorRounding(mTopLevelDrawable, mRoundingParams); for (int i = 0; i < mFadeDrawable.getNumberOfLayers(); i++) { WrappingUtils.updateLeafRounding(getParentDrawableAtIndex(i), mRoundingParams, mResources); } } /** Gets the rounding params. */ @Nullable public RoundingParams getRoundingParams() { return mRoundingParams; } @VisibleForTesting public boolean hasImage() { return mActualImageWrapper.getDrawable() != mEmptyActualImageDrawable; } public void setOnFadeListener(OnFadeListener onFadeListener) { mFadeDrawable.setOnFadeListener(onFadeListener); } } ","focusPoint "