Java tutorial
// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.client.editor.youngandroid; import static com.google.appinventor.client.Ode.MESSAGES; import com.google.appinventor.client.Ode; import com.google.appinventor.client.OdeAsyncCallback; import com.google.appinventor.client.boxes.AssetListBox; import com.google.appinventor.client.boxes.PaletteBox; import com.google.appinventor.client.boxes.PropertiesBox; import com.google.appinventor.client.boxes.SourceStructureBox; import com.google.appinventor.client.editor.ProjectEditor; import com.google.appinventor.client.editor.simple.SimpleComponentDatabase; import com.google.appinventor.client.editor.simple.SimpleEditor; import com.google.appinventor.client.editor.simple.SimpleNonVisibleComponentsPanel; import com.google.appinventor.client.editor.simple.SimpleVisibleComponentsPanel; import com.google.appinventor.client.editor.simple.components.FormChangeListener; import com.google.appinventor.client.editor.simple.components.MockComponent; import com.google.appinventor.client.editor.simple.components.MockContainer; import com.google.appinventor.client.editor.simple.components.MockForm; import com.google.appinventor.client.editor.simple.palette.DropTargetProvider; import com.google.appinventor.client.editor.simple.palette.SimpleComponentDescriptor; import com.google.appinventor.client.editor.simple.palette.SimplePalettePanel; import com.google.appinventor.client.editor.youngandroid.palette.YoungAndroidPalettePanel; import com.google.appinventor.client.explorer.SourceStructureExplorer; import com.google.appinventor.client.explorer.project.ComponentDatabaseChangeListener; import com.google.appinventor.client.output.OdeLog; import com.google.appinventor.client.properties.json.ClientJsonParser; import com.google.appinventor.client.properties.json.ClientJsonString; import com.google.appinventor.client.widgets.dnd.DropTarget; import com.google.appinventor.client.widgets.properties.EditableProperties; import com.google.appinventor.client.widgets.properties.PropertiesPanel; import com.google.appinventor.client.youngandroid.YoungAndroidFormUpgrader; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.shared.properties.json.JSONArray; import com.google.appinventor.shared.properties.json.JSONObject; import com.google.appinventor.shared.properties.json.JSONParser; import com.google.appinventor.shared.properties.json.JSONValue; import com.google.appinventor.shared.rpc.project.ChecksumedFileException; import com.google.appinventor.shared.rpc.project.ChecksumedLoadFile; import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidFormNode; import com.google.appinventor.shared.youngandroid.YoungAndroidSourceAnalyzer; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.DockPanel; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Editor for Young Android Form (.scm) files. * * <p>This editor shows a designer that provides support for visual design of * forms.</p> * * @author markf@google.com (Mark Friedman) * @author lizlooney@google.com (Liz Looney) */ public final class YaFormEditor extends SimpleEditor implements FormChangeListener, ComponentDatabaseChangeListener { private static class FileContentHolder { private String content; FileContentHolder(String content) { this.content = content; } void setFileContent(String content) { this.content = content; } String getFileContent() { return content; } } // JSON parser private static final JSONParser JSON_PARSER = new ClientJsonParser(); private final SimpleComponentDatabase COMPONENT_DATABASE; private final YoungAndroidFormNode formNode; // Flag to indicate when loading the file is completed. This is needed because building the mock // form from the file properties fires events that need to be ignored, otherwise the file will be // marked as being modified. private boolean loadComplete; // References to other panels that we need to control. private final SourceStructureExplorer sourceStructureExplorer; // Panels that are used as the content of the palette and properties boxes. private final YoungAndroidPalettePanel palettePanel; private final PropertiesPanel designProperties; // UI elements private final SimpleVisibleComponentsPanel visibleComponentsPanel; private final SimpleNonVisibleComponentsPanel nonVisibleComponentsPanel; private MockForm form; // initialized lazily after the file is loaded from the ODE server // [lyn, 2014/10/13] Need to remember JSON initially loaded from .scm file *before* it is upgraded // by YoungAndroidFormUpgrader within upgradeFile. This JSON contains pre-upgrade component // version info that is needed by Blockly.SaveFile.load to perform upgrades in the Blocks Editor. // This was unnecessary in AI Classic because the .blk file contained component version info // as well as the .scm file. But in AI2, the .bky file contains no component version info, // and we rely on the pre-upgraded .scm file for this info. private String preUpgradeJsonString; private final List<ComponentDatabaseChangeListener> componentDatabaseChangeListeners = new ArrayList<ComponentDatabaseChangeListener>(); private JSONArray authURL; // List of App Inventor versions we have been edited on. private static final int OLD_PROJECT_YAV = 150; // Projects older then this have no authURL /** * Creates a new YaFormEditor. * * @param projectEditor the project editor that contains this file editor * @param formNode the YoungAndroidFormNode associated with this YaFormEditor */ YaFormEditor(ProjectEditor projectEditor, YoungAndroidFormNode formNode) { super(projectEditor, formNode); this.formNode = formNode; COMPONENT_DATABASE = SimpleComponentDatabase.getInstance(getProjectId()); // Get reference to the source structure explorer sourceStructureExplorer = SourceStructureBox.getSourceStructureBox().getSourceStructureExplorer(); // Create UI elements for the designer panels. nonVisibleComponentsPanel = new SimpleNonVisibleComponentsPanel(); visibleComponentsPanel = new SimpleVisibleComponentsPanel(this, nonVisibleComponentsPanel); DockPanel componentsPanel = new DockPanel(); componentsPanel.setHorizontalAlignment(DockPanel.ALIGN_CENTER); componentsPanel.add(visibleComponentsPanel, DockPanel.NORTH); componentsPanel.add(nonVisibleComponentsPanel, DockPanel.SOUTH); componentsPanel.setSize("100%", "100%"); // Create palettePanel, which will be used as the content of the PaletteBox. palettePanel = new YoungAndroidPalettePanel(this); palettePanel.loadComponents(new DropTargetProvider() { @Override public DropTarget[] getDropTargets() { // TODO(markf): Figure out a good way to memorize the targets or refactor things so that // getDropTargets() doesn't get called for each component. // NOTE: These targets must be specified in depth-first order. List<DropTarget> dropTargets = form.getDropTargetsWithin(); dropTargets.add(visibleComponentsPanel); dropTargets.add(nonVisibleComponentsPanel); return dropTargets.toArray(new DropTarget[dropTargets.size()]); } }); palettePanel.setSize("100%", "100%"); addComponentDatabaseChangeListener(palettePanel); // Create designProperties, which will be used as the content of the PropertiesBox. designProperties = new PropertiesPanel(); designProperties.setSize("100%", "100%"); initWidget(componentsPanel); setSize("100%", "100%"); } // FileEditor methods @Override public void loadFile(final Command afterFileLoaded) { final long projectId = getProjectId(); final String fileId = getFileId(); OdeAsyncCallback<ChecksumedLoadFile> callback = new OdeAsyncCallback<ChecksumedLoadFile>( MESSAGES.loadError()) { @Override public void onSuccess(ChecksumedLoadFile result) { String contents; try { contents = result.getContent(); } catch (ChecksumedFileException e) { this.onFailure(e); return; } final FileContentHolder fileContentHolder = new FileContentHolder(contents); upgradeFile(fileContentHolder, new Command() { @Override public void execute() { onFileLoaded(fileContentHolder.getFileContent()); if (afterFileLoaded != null) { afterFileLoaded.execute(); } } }); } @Override public void onFailure(Throwable caught) { if (caught instanceof ChecksumedFileException) { Ode.getInstance().recordCorruptProject(projectId, fileId, caught.getMessage()); } super.onFailure(caught); } }; Ode.getInstance().getProjectService().load2(projectId, fileId, callback); } @Override public String getTabText() { return formNode.getFormName(); } @Override public void onShow() { OdeLog.log("YaFormEditor: got onShow() for " + getFileId()); super.onShow(); loadDesigner(); } @Override public void onHide() { OdeLog.log("YaFormEditor: got onHide() for " + getFileId()); // When an editor is detached, if we are the "current" editor, // set the current editor to null and clean up the UI. // Note: I'm not sure it is possible that we would not be the "current" // editor when this is called, but we check just to be safe. if (Ode.getInstance().getCurrentFileEditor() == this) { super.onHide(); unloadDesigner(); } else { OdeLog.wlog("YaFormEditor.onHide: Not doing anything since we're not the " + "current file editor!"); } } @Override public void onClose() { form.removeFormChangeListener(this); // Note: our partner YaBlocksEditor will remove itself as a FormChangeListener, even // though we added it. } @Override public String getRawFileContent() { String encodedProperties = encodeFormAsJsonString(false); JSONObject propertiesObject = JSON_PARSER.parse(encodedProperties).asObject(); return YoungAndroidSourceAnalyzer.generateSourceFile(propertiesObject); } @Override public void onSave() { } // SimpleEditor methods @Override public boolean isLoadComplete() { return loadComplete; } @Override public Map<String, MockComponent> getComponents() { Map<String, MockComponent> map = Maps.newHashMap(); if (loadComplete) { populateComponentsMap(form, map); } return map; } @Override public List<String> getComponentNames() { return new ArrayList<String>(getComponents().keySet()); } @Override public SimplePalettePanel getComponentPalettePanel() { return palettePanel; } @Override public SimpleNonVisibleComponentsPanel getNonVisibleComponentsPanel() { return nonVisibleComponentsPanel; } public SimpleVisibleComponentsPanel getVisibleComponentsPanel() { return visibleComponentsPanel; } @Override public boolean isScreen1() { return formNode.isScreen1(); } // FormChangeListener implementation @Override public void onComponentPropertyChanged(MockComponent component, String propertyName, String propertyValue) { if (loadComplete) { // If the property isn't actually persisted to the .scm file, we don't need to do anything. if (component.isPropertyPersisted(propertyName)) { Ode.getInstance().getEditorManager().scheduleAutoSave(this); updatePhone(); // Push changes to the phone if it is connected } } else { OdeLog.elog("onComponentPropertyChanged called when loadComplete is false"); } } @Override public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) { if (loadComplete) { if (permanentlyDeleted) { onFormStructureChange(); } } else { OdeLog.elog("onComponentRemoved called when loadComplete is false"); } } @Override public void onComponentAdded(MockComponent component) { if (loadComplete) { onFormStructureChange(); } else { OdeLog.elog("onComponentAdded called when loadComplete is false"); } } @Override public void onComponentRenamed(MockComponent component, String oldName) { if (loadComplete) { onFormStructureChange(); updatePropertiesPanel(component); } else { OdeLog.elog("onComponentRenamed called when loadComplete is false"); } } @Override public void onComponentSelectionChange(MockComponent component, boolean selected) { if (loadComplete) { if (selected) { // Select the item in the source structure explorer. sourceStructureExplorer.selectItem(component.getSourceStructureExplorerItem()); // Show the component properties in the properties panel. updatePropertiesPanel(component); } else { // Unselect the item in the source structure explorer. sourceStructureExplorer.unselectItem(component.getSourceStructureExplorerItem()); } } else { OdeLog.elog("onComponentSelectionChange called when loadComplete is false"); } } // other public methods /** * Returns the form associated with this YaFormEditor. * * @return a MockForm */ public MockForm getForm() { return form; } public String getComponentInstanceTypeName(String instanceName) { return getComponents().get(instanceName).getType(); } // private methods /* * Upgrades the given file content, saves the upgraded content back to the * ODE server, and calls the afterUpgradeComplete command after the save * operation succeeds. * * If no upgrade is necessary, the afterSavingFiles command is called * immediately. * * @param fileContentHolder holds the file content * @param afterUpgradeComplete optional command to be executed after the * file has upgraded and saved back to the ODE * server */ private void upgradeFile(FileContentHolder fileContentHolder, final Command afterUpgradeComplete) { JSONObject propertiesObject = YoungAndroidSourceAnalyzer.parseSourceFile(fileContentHolder.getFileContent(), JSON_PARSER); // BEGIN PROJECT TAGGING CODE // |-------------------------------------------------------------------| // | Project Tagging Code: | // | Because of the likely proliferation of various versions of App | // | Inventor, we want to mark a project with the history of which | // | versions have seen it. We do that with the "authURL" tag which we | // | add to the Form files. It is a JSON array of versions identified | // | by the hostname portion of the URL of the service editing the | // | project. Older projects will not have this field, so if we detect | // | an older project (YAV < OLD_PROJECT_YAV) we create the list and | // | add ourselves. If we read in a project where YAV >= | // | OLD_PROJECT_YAV *and* there is no authURL, we assume that it was | // | created on a version of App Inventor that doesn't support project | // | tagging and we add an "*UNKNOWN*" tag to indicate this. So for | // | example if you examine a (newer) project and look in the | // | Screen1.scm file, you should just see an authURL that looks like | // | ["ai2.appinventor.mit.edu"]. This would indicate a project that | // | has only been edited on MIT App Inventor. If instead you see | // | something like ["localhost", "ai2.appinventor.mit.edu"] it | // | implies that at some point in its history this project was edited | // | using the local dev server on someone's own computer. | // |-------------------------------------------------------------------| authURL = (JSONArray) propertiesObject.get("authURL"); String ourHost = Window.Location.getHostName(); JSONValue us = new ClientJsonString(ourHost); if (authURL != null) { List<JSONValue> values = authURL.asArray().getElements(); boolean foundUs = false; for (JSONValue value : values) { if (value.asString().getString().equals(ourHost)) { foundUs = true; break; } } if (!foundUs) { authURL.asArray().getElements().add(us); } } else { // Kludgey way to create an empty JSON array. But we cannot call ClientJsonArray ourselves // because it is not a public class. So rather then make it public (and violate an abstraction // barrier). We create the array this way. Sigh. authURL = JSON_PARSER.parse("[]").asArray(); // Warning: If YaVersion isn't present, we will get an NPF on // the line below. But it should always be there... // Note: YaVersion although a numeric value is stored as a Json String so we have // to parse it as a string and then convert it to a number in Java. int yav = Integer.parseInt(propertiesObject.get("YaVersion").asString().getString()); // If yav is > OLD_PROJECT_YAV, and we still don't have an // authURL property then we likely originated from a non-MIT App // Inventor instance so add an *Unknown* tag before our tag if (yav > OLD_PROJECT_YAV) { authURL.asArray().getElements().add(new ClientJsonString("*UNKNOWN*")); } authURL.asArray().getElements().add(us); } // END OF PROJECT TAGGING CODE preUpgradeJsonString = propertiesObject.toJson(); // [lyn, [2014/10/13] remember pre-upgrade component versions. if (YoungAndroidFormUpgrader.upgradeSourceProperties(propertiesObject.getProperties())) { String upgradedContent = YoungAndroidSourceAnalyzer.generateSourceFile(propertiesObject); fileContentHolder.setFileContent(upgradedContent); Ode.getInstance().getProjectService().save(Ode.getInstance().getSessionId(), getProjectId(), getFileId(), upgradedContent, new OdeAsyncCallback<Long>(MESSAGES.saveError()) { @Override public void onSuccess(Long result) { // Execute the afterUpgradeComplete command if one was given. if (afterUpgradeComplete != null) { afterUpgradeComplete.execute(); } } }); } else { // No upgrade was necessary. // Execute the afterUpgradeComplete command if one was given. if (afterUpgradeComplete != null) { afterUpgradeComplete.execute(); } } } private void onFileLoaded(String content) { JSONObject propertiesObject = YoungAndroidSourceAnalyzer.parseSourceFile(content, JSON_PARSER); form = createMockForm(propertiesObject.getProperties().get("Properties").asObject()); // Initialize the nonVisibleComponentsPanel and visibleComponentsPanel. nonVisibleComponentsPanel.setForm(form); visibleComponentsPanel.setForm(form); form.select(); // Set loadCompleted to true. // From now on, all change events will be taken seriously. loadComplete = true; } /* * Parses the JSON properties and creates the form and its component structure. */ private MockForm createMockForm(JSONObject propertiesObject) { return (MockForm) createMockComponent(propertiesObject, null); } /* * Parses the JSON properties and creates the component structure. This method is called * recursively for nested components. For the initial invocation parent shall be null. */ private MockComponent createMockComponent(JSONObject propertiesObject, MockContainer parent) { Map<String, JSONValue> properties = propertiesObject.getProperties(); // Component name and type String componentType = properties.get("$Type").asString().getString(); // Instantiate a mock component for the visual designer MockComponent mockComponent; if (componentType.equals(MockForm.TYPE)) { Preconditions.checkArgument(parent == null); // Instantiate new root component mockComponent = new MockForm(this); } else { mockComponent = SimpleComponentDescriptor.createMockComponent(componentType, this); // Add the component to its parent component (and if it is non-visible, add it to the // nonVisibleComponent panel). parent.addComponent(mockComponent); if (!mockComponent.isVisibleComponent()) { nonVisibleComponentsPanel.addComponent(mockComponent); } } // Set the name of the component (on instantiation components are assigned a generated name) String componentName = properties.get("$Name").asString().getString(); mockComponent.changeProperty("Name", componentName); // Set component properties for (String name : properties.keySet()) { if (name.charAt(0) != '$') { // Ignore special properties (name, type and nested components) mockComponent.changeProperty(name, properties.get(name).asString().getString()); } } //This is for old project which doesn't have the AppName property if (mockComponent instanceof MockForm) { if (!properties.keySet().contains("AppName")) { String fileId = getFileId(); String projectName = fileId.split("/")[3]; mockComponent.changeProperty("AppName", projectName); } } // Add component type to the blocks editor YaProjectEditor yaProjectEditor = (YaProjectEditor) projectEditor; YaBlocksEditor blockEditor = yaProjectEditor.getBlocksFileEditor(formNode.getFormName()); blockEditor.addComponent(mockComponent.getType(), mockComponent.getName(), mockComponent.getUuid()); // Add nested components if (properties.containsKey("$Components")) { for (JSONValue nestedComponent : properties.get("$Components").asArray().getElements()) { createMockComponent(nestedComponent.asObject(), (MockContainer) mockComponent); } } return mockComponent; } /* * Updates the the whole designer: form, palette, source structure explorer, * assets list, and properties panel. */ private void loadDesigner() { form.refresh(); MockComponent selectedComponent = form.getSelectedComponent(); // Set the palette box's content. PaletteBox paletteBox = PaletteBox.getPaletteBox(); paletteBox.setContent(palettePanel); // Update the source structure explorer with the tree of this form's components. sourceStructureExplorer.updateTree(form.buildComponentsTree(), selectedComponent.getSourceStructureExplorerItem()); SourceStructureBox.getSourceStructureBox().setVisible(true); // Show the assets box. AssetListBox assetListBox = AssetListBox.getAssetListBox(); assetListBox.setVisible(true); // Set the properties box's content. PropertiesBox propertiesBox = PropertiesBox.getPropertiesBox(); propertiesBox.setContent(designProperties); updatePropertiesPanel(selectedComponent); propertiesBox.setVisible(true); // Listen to changes on the form. form.addFormChangeListener(this); // Also have the blocks editor listen to changes. Do this here instead // of in the blocks editor so that we don't risk it missing any updates. OdeLog.log("Adding blocks editor as a listener for " + form.getName()); form.addFormChangeListener(((YaProjectEditor) projectEditor).getBlocksFileEditor(form.getName())); } /* * Show the given component's properties in the properties panel. */ private void updatePropertiesPanel(MockComponent component) { designProperties.setProperties(component.getProperties()); // need to update the caption after the setProperties call, since // setProperties clears the caption! designProperties.setPropertiesCaption(component.getName()); } private void onFormStructureChange() { Ode.getInstance().getEditorManager().scheduleAutoSave(this); // Update source structure panel sourceStructureExplorer.updateTree(form.buildComponentsTree(), form.getSelectedComponent().getSourceStructureExplorerItem()); updatePhone(); // Push changes to the phone if it is connected } private void populateComponentsMap(MockComponent component, Map<String, MockComponent> map) { EditableProperties properties = component.getProperties(); map.put(properties.getPropertyValue("Name"), component); List<MockComponent> children = component.getChildren(); for (MockComponent child : children) { populateComponentsMap(child, map); } } /* * Encodes the form's properties as a JSON encoded string. Used by YaBlocksEditor as well, * to send the form info to the blockly world during code generation. */ protected String encodeFormAsJsonString(boolean forYail) { StringBuilder sb = new StringBuilder(); sb.append("{"); // Include authURL in output if it is non-null if (authURL != null) { sb.append("\"authURL\":").append(authURL.toJson()).append(","); } sb.append("\"YaVersion\":\"").append(YaVersion.YOUNG_ANDROID_VERSION).append("\","); sb.append("\"Source\":\"Form\","); sb.append("\"Properties\":"); encodeComponentProperties(form, sb, forYail); sb.append("}"); return sb.toString(); } // [lyn, 2014/10/13] returns the *pre-upgraded* JSON for this form. // needed to allow associated blocks editor to get this info. protected String preUpgradeJsonString() { return preUpgradeJsonString; } /* * Encodes a component and its properties into a JSON encoded string. */ private void encodeComponentProperties(MockComponent component, StringBuilder sb, boolean forYail) { // The component encoding starts with component name and type String componentType = component.getType(); EditableProperties properties = component.getProperties(); sb.append("{\"$Name\":\""); sb.append(properties.getPropertyValue("Name")); sb.append("\",\"$Type\":\""); sb.append(componentType); sb.append("\",\"$Version\":\""); sb.append(COMPONENT_DATABASE.getComponentVersion(componentType)); sb.append('"'); // Next the actual component properties // // NOTE: It is important that these be encoded before any children components. String propertiesString = properties.encodeAsPairs(forYail); if (propertiesString.length() > 0) { sb.append(','); sb.append(propertiesString); } // Finally any children of the component List<MockComponent> children = component.getChildren(); if (!children.isEmpty()) { sb.append(",\"$Components\":["); String separator = ""; for (MockComponent child : children) { sb.append(separator); encodeComponentProperties(child, sb, forYail); separator = ","; } sb.append(']'); } sb.append('}'); } /* * Clears the palette, source structure explorer, and properties panel. */ private void unloadDesigner() { // The form can still potentially change if the blocks editor is displayed // so don't remove the formChangeListener. // Clear the palette box. PaletteBox paletteBox = PaletteBox.getPaletteBox(); paletteBox.clear(); // Clear and hide the source structure explorer. sourceStructureExplorer.clearTree(); SourceStructureBox.getSourceStructureBox().setVisible(false); // Hide the assets box. AssetListBox assetListBox = AssetListBox.getAssetListBox(); assetListBox.setVisible(false); // Clear and hide the properties box. PropertiesBox propertiesBox = PropertiesBox.getPropertiesBox(); propertiesBox.clear(); propertiesBox.setVisible(false); } /* * Push changes to a connected phone (or emulator). */ private void updatePhone() { YaProjectEditor yaProjectEditor = (YaProjectEditor) projectEditor; YaBlocksEditor blockEditor = yaProjectEditor.getBlocksFileEditor(formNode.getFormName()); blockEditor.onBlocksAreaChanged(getProjectId() + "_" + formNode.getFormName()); } private void addComponentDatabaseChangeListener(ComponentDatabaseChangeListener cdbChangeListener) { componentDatabaseChangeListeners.add(cdbChangeListener); } private void removeComponentDatabaseChangeListener(ComponentDatabaseChangeListener cdbChangeListener) { componentDatabaseChangeListeners.remove(cdbChangeListener); } private void clearComponentDatabaseChangeListener() { componentDatabaseChangeListeners.clear(); } @Override public void onComponentTypeAdded(List<String> componentTypes) { COMPONENT_DATABASE.removeComponentDatabaseListener(this); for (ComponentDatabaseChangeListener cdbChangeListener : componentDatabaseChangeListeners) { cdbChangeListener.onComponentTypeAdded(componentTypes); } } @Override public boolean beforeComponentTypeRemoved(List<String> componentTypes) { boolean result = true; for (ComponentDatabaseChangeListener cdbChangeListener : componentDatabaseChangeListeners) { result = result & cdbChangeListener.beforeComponentTypeRemoved(componentTypes); } List<MockComponent> mockComponents = new ArrayList<MockComponent>(getForm().getChildren()); for (String compType : componentTypes) { for (MockComponent mockComp : mockComponents) { if (mockComp.getType().equals(compType)) { mockComp.delete(); } } } return result; } @Override public void onComponentTypeRemoved(Map<String, String> componentTypes) { COMPONENT_DATABASE.removeComponentDatabaseListener(this); for (ComponentDatabaseChangeListener cdbChangeListener : componentDatabaseChangeListeners) { cdbChangeListener.onComponentTypeRemoved(componentTypes); } } @Override public void onResetDatabase() { COMPONENT_DATABASE.removeComponentDatabaseListener(this); for (ComponentDatabaseChangeListener cdbChangeListener : componentDatabaseChangeListeners) { cdbChangeListener.onResetDatabase(); } } }