diff --git a/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java b/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java index 4fe7969385e..66220ae64db 100755 --- a/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java +++ b/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java @@ -4897,6 +4897,10 @@ String newerVersionComponentException(String componentType, int srcCompVersion, @Description("") String createWelcomeDialogButton(); + @DefaultMessage("Loading screen {0} ({1} seconds estimated remaining)...") + @Description("") + String loadingScreen(String screenName, int secondsRemaining); + @DefaultMessage("Do Not Show Again") @Description("") String doNotShow(); @@ -4992,6 +4996,18 @@ String newerVersionComponentException(String componentType, int srcCompVersion, @Description("") String corruptionDialogText(); + @DefaultMessage("Project {0} is missing Screen1. Refusing to continue loading the project.") + @Description("") + String screen1MissingText(String projectName); + + @DefaultMessage("Project {0} is missing a designer file for screen {1}.") + @Description("") + String screenMissingDesigner(String projectName, String formName); + + @DefaultMessage("Project {0} is missing a blocks file for screen {1}.") + @Description("") + String screenMissingBlocks(String projectName, String formName); + @DefaultMessage("

We detected errors while reading in your project

" + "

To protect your project from damage, we have ended this session. You may close this " + "window.

") diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/FileEditor.java b/appinventor/appengine/src/com/google/appinventor/client/editor/FileEditor.java index 95daf84e404..90e42579b59 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/FileEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/FileEditor.java @@ -1,13 +1,12 @@ // -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved -// Copyright 2011-2012 MIT, All rights reserved +// Copyright 2011-2019 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; import com.google.appinventor.client.Ode; -import com.google.appinventor.client.editor.simple.palette.DropTargetProvider; import com.google.appinventor.shared.rpc.project.FileNode; import com.google.appinventor.shared.rpc.project.ProjectRootNode; import com.google.gwt.core.client.Callback; diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java index efb877c57cf..eb4d53ca626 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java @@ -1,6 +1,6 @@ // -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved -// Copyright 2011-2012 MIT, All rights reserved +// Copyright 2011-2019 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 @@ -8,6 +8,8 @@ import com.google.appinventor.client.ErrorReporter; import com.google.appinventor.client.Ode; +import static com.google.appinventor.client.Ode.MESSAGES; + import com.google.appinventor.client.editor.FileEditor; import com.google.appinventor.client.editor.ProjectEditor; import com.google.appinventor.client.editor.youngandroid.actions.SwitchScreenAction; @@ -29,8 +31,6 @@ import java.util.Map; import java.util.logging.Logger; -import static com.google.appinventor.client.Ode.MESSAGES; - /** * The design toolbar houses command buttons in the Young Android Design * tab (for the UI designer (a.k.a, Form Editor) and Blocks Editor). @@ -70,7 +70,7 @@ public static class DesignProject { public DesignProject(String name, long projectId) { this.name = name; this.projectId = projectId; - screens = Maps.newHashMap(); + screens = Maps.newTreeMap(); // Screen1 is initial screen by default currentScreen = YoungAndroidSourceNode.SCREEN1_FORM_NAME; // Let BlocklyPanel know which screen to send Yail for @@ -245,16 +245,10 @@ private boolean switchToProject(long projectId, String projectName) { return true; } pushedScreens.clear(); // Effectively switching applications; clear stack of screens. - clearDropDownMenu(WIDGET_NAME_SCREENS_DROPDOWN); LOG.info("DesignToolbar: switching to existing project " + projectName + " with id " + projectId); currentProject = project; - - // TODO(sharon): add screens to drop-down menu in the right order - for (Screen screen : currentProject.screens.values()) { - addDropDownButtonItem(WIDGET_NAME_SCREENS_DROPDOWN, new DropDownItem(screen.screenName, - screen.screenName, new SwitchScreenAction(projectId, screen.screenName))); - } + sortScreenList(projectId); projectNameLabel.setText(projectName); YaBlocksEditor.resendAssetsAndExtensions(); // Send assets for active project } else { @@ -267,6 +261,17 @@ private boolean switchToProject(long projectId, String projectName) { return true; } + public void sortScreenList(long projectId) { + if (currentProject != null) { // We can only sort screens if we have a project. + clearDropDownMenu(WIDGET_NAME_SCREENS_DROPDOWN); + // TODO(sharon): add screens to drop-down menu in the right order + for (Screen screen : currentProject.screens.values()) { + addDropDownButtonItem(WIDGET_NAME_SCREENS_DROPDOWN, new DropDownItem(screen.screenName, + screen.screenName, new SwitchScreenAction(projectId, screen.screenName))); + } + } + } + /* * Add a screen name to the drop-down for the project with id projectId. * name is the form name, formEditor is the file editor for the form UI, @@ -282,8 +287,7 @@ public void addScreen(long projectId, String name, FileEditor formEditor, DesignProject project = projectMap.get(projectId); if (project.addScreen(name, formEditor, blocksEditor)) { if (currentProject == project) { - addDropDownButtonItem(WIDGET_NAME_SCREENS_DROPDOWN, new DropDownItem(name, - name, new SwitchScreenAction(projectId, name))); + sortScreenList(projectId); } } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java index 203e9e593b9..e82711f8955 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java @@ -212,7 +212,7 @@ public void loadFile(final Command afterFileLoaded) { final String fileId = getFileId(); OdeAsyncCallback callback = new OdeAsyncCallback(MESSAGES.loadError()) { @Override - public void onSuccess(ChecksumedLoadFile result) { + public void onSuccess(final ChecksumedLoadFile result) { String contents; try { contents = result.getContent(); @@ -229,6 +229,11 @@ public void execute() { } catch(IllegalArgumentException e) { return; } + try { + result.setContent(fileContentHolder.getFileContent()); + } catch (ChecksumedFileException e) { + LOG.warning("ChecksumedFileException in YaFormEditor"); + } if (afterFileLoaded != null) { afterFileLoaded.execute(); } diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaProjectEditor.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaProjectEditor.java index 34e708c0123..748e8e52db8 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaProjectEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaProjectEditor.java @@ -20,6 +20,8 @@ import com.google.appinventor.client.editor.simple.components.MockFusionTablesControl; import com.google.appinventor.client.editor.simple.components.MockTwitter; import com.google.appinventor.client.explorer.dialogs.ProjectPropertiesDialogBox; +import com.google.appinventor.client.editor.youngandroid.i18n.BlocklyMsg; +import com.google.appinventor.client.explorer.dialogs.ProgressBarDialogBox; import com.google.appinventor.client.explorer.project.ComponentDatabaseChangeListener; import com.google.appinventor.client.explorer.project.Project; import com.google.appinventor.client.explorer.project.ProjectChangeListener; @@ -33,6 +35,7 @@ import com.google.appinventor.shared.rpc.project.ChecksumedLoadFile; import com.google.appinventor.shared.rpc.project.ProjectNode; import com.google.appinventor.shared.rpc.project.ProjectRootNode; +import com.google.appinventor.shared.rpc.project.SourceNode; import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidBlocksNode; import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidComponentsFolder; import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidFormNode; @@ -52,8 +55,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -87,6 +93,8 @@ interface CombinedUi extends UiBinder {} private static class EditorSet { YaFormEditor formEditor = null; YaBlocksEditor blocksEditor = null; + YoungAndroidFormNode formNode = null; + YoungAndroidBlocksNode blocksNode = null; } // Maps form name -> editors for this form @@ -103,6 +111,8 @@ private static class EditorSet { // Database of component type descriptions private final SimpleComponentDatabase COMPONENT_DATABASE; + private final Deque loadingEditors = new LinkedList<>(); + // State variables to help determine whether we are ready to show Screen1 // Automatically select the Screen1 form editor when we have finished loading // both the form and blocks editors for Screen1 and we have added the @@ -147,32 +157,38 @@ public YaProjectEditor(ProjectRootNode projectRootNode) { COMPONENT_DATABASE = SimpleComponentDatabase.getInstance(projectId); } - private void loadBlocksEditor(String formNamePassedIn) { + private void loadBlocksEditor(String formNamePassedIn, final Runnable callback) { final String formName = formNamePassedIn; - final YaBlocksEditor newBlocksEditor = editorMap.get(formName).blocksEditor; + final EditorSet editors = editorMap.get(formName); + final YaBlocksEditor newBlocksEditor = editors.blocksEditor; newBlocksEditor.loadFile(new Command() { - @Override - public void execute() { - YaBlocksEditor newBlocksEditor = editorMap.get(formName).blocksEditor; - int pos = Collections.binarySearch(fileIds, newBlocksEditor.getFileId(), - getFileIdComparator()); - if (pos < 0) { - pos = -pos - 1; - } - insertFileEditor(newBlocksEditor, pos); - if (isScreen1(formName)) { - screen1BlocksLoaded = true; - if (readyToShowScreen1()) { - LOG.info("YaProjectEditor.addBlocksEditor.loadFile.execute: switching to screen " - + formName + " for project " + newBlocksEditor.getProjectId()); - Ode.getInstance().getDesignToolbar().switchToScreen(newBlocksEditor.getProjectId(), - formName, DesignToolbar.View.FORM); - } + @Override + public void execute() { + final EditorSet editors = editorMap.get(formName); + YaBlocksEditor newBlocksEditor = editors.blocksEditor; + LOG.info("Loaded " + formName + ".bky"); + loadingEditors.remove(editors); + int pos = Collections.binarySearch(fileIds, newBlocksEditor.getFileId(), + getFileIdComparator()); + if (pos < 0) { + pos = -pos - 1; + } + insertFileEditor(newBlocksEditor, pos); + if (isScreen1(formName)) { + screen1BlocksLoaded = true; + if (readyToShowScreen1()) { + LOG.info("YaProjectEditor.addBlocksEditor.loadFile.execute: switching to screen " + + formName + " for project " + newBlocksEditor.getProjectId()); + Ode.getInstance().getDesignToolbar().switchToScreen(newBlocksEditor.getProjectId(), + formName, DesignToolbar.View.FORM); } } - }); - + if (callback != null) { + callback.run(); + } + } + }); } /** @@ -187,47 +203,107 @@ public void processProject() { .then(this::loadProject); } + private EditorSet getOrCreateEditorSet(String formName) { + EditorSet result = editorMap.get(formName); + if (result == null) { + result = new EditorSet(); + editorMap.put(formName, result); + } + return result; + } + // Note: When we add the blocks editors in the loop below we do not actually // have them load the blocks file. Instead we trigger the load of a blocks file // in the callback for the loading of its associated forms file. This is important // because we have to ensure that the component type data is available when the // blocks are loaded! - private Promise loadProject(Object result) { // add form editors first, then blocks editors because the blocks editors // need access to their corresponding form editors to set up properly - for (ProjectNode source : projectRootNode.getAllSourceNodes()) { + final long start = System.currentTimeMillis(); + YoungAndroidFormNode screen1scm = null; + YoungAndroidBlocksNode screen1bky = null; + for (SourceNode source : projectRootNode.getAllSourceNodes()) { if (source instanceof YoungAndroidFormNode) { - addFormEditor((YoungAndroidFormNode) source); - } - } - for (ProjectNode source: projectRootNode.getAllSourceNodes()) { - if (source instanceof YoungAndroidBlocksNode) { - addBlocksEditor((YoungAndroidBlocksNode) source); + YoungAndroidFormNode form = (YoungAndroidFormNode) source; + if (form.isScreen1()) { + screen1scm = form; + } + getOrCreateEditorSet(form.getFormName()).formNode = form; + } else if (source instanceof YoungAndroidBlocksNode) { + YoungAndroidBlocksNode blocks = (YoungAndroidBlocksNode) source; + if (blocks.isScreen1()) { + screen1bky = blocks; + } + getOrCreateEditorSet(blocks.getFormName()).blocksNode = blocks; } } + if (screen1scm == null || screen1bky == null) { + Ode.getInstance().genericWarning(MESSAGES.screen1MissingText(project.getProjectName())); + } else { + final ProgressBarDialogBox progress = new ProgressBarDialogBox("MESSAGES.loadingAppIndicatorText()", projectRootNode); + progress.show(); + progress.center(); - // Add the screens to the design toolbar, along with their associated editors - DesignToolbar designToolbar = Ode.getInstance().getDesignToolbar(); - for (String formName : editorMap.keySet()) { - EditorSet editors = editorMap.get(formName); - if (editors.formEditor != null && editors.blocksEditor != null) { - designToolbar.addScreen(projectRootNode.getProjectId(), formName, editors.formEditor, - editors.blocksEditor); - if (isScreen1(formName)) { - screen1Added = true; - if (readyToShowScreen1()) { // probably not yet but who knows? - LOG.info("YaProjectEditor.loadProject: switching to screen " + formName - + " for project " + projectRootNode.getProjectId()); - Ode.getInstance().getDesignToolbar().switchToScreen(projectRootNode.getProjectId(), - formName, DesignToolbar.View.FORM); + final DesignToolbar designToolbar = Ode.getInstance().getDesignToolbar(); + final Runnable callback = new Runnable() { + int counter = 0; + + @Override + public void run() { + counter++; + if (loadingEditors.isEmpty() && counter >= editorMap.size()) { + // All done... + progress.hide(); + designToolbar.sortScreenList(projectId); + Ode.CLog("Loading project took " + (System.currentTimeMillis() - start) + " ms"); + } else { + double timeRemaining = (double) (System.currentTimeMillis() - start) / counter * + (editorMap.size() - counter) / 1000.0; + progress.setProgress(100 * counter / editorMap.size(), + MESSAGES.loadingScreen(loadingEditors.getFirst().formNode.getFormName(), (int) timeRemaining)); } } - } else if (editors.formEditor == null) { - LOG.warning("Missing form editor for " + formName); - } else { - LOG.warning("Missing blocks editor for " + formName); + }; + addFormEditor(screen1scm, callback); + addBlocksEditor(screen1bky); + + final String screen1 = screen1scm.getFormName(); + EditorSet editors = editorMap.get(screen1); + designToolbar.addScreen(projectRootNode.getProjectId(), screen1, editors.formEditor, editors.blocksEditor); + screen1Added = true; + if (readyToShowScreen1()) { + Ode.CLog("YaProjectEditor.loadProject: switching to screen " + screen1 + + " for project " + projectRootNode.getProjectId()); + Ode.getInstance().getDesignToolbar().switchToScreen(projectRootNode.getProjectId(), + screen1, DesignToolbar.View.FORM); } + + Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { + private Iterator> it = editorMap.entrySet().iterator(); + + @Override + public void execute() { + if (it.hasNext()) { + Map.Entry toLoad = it.next(); + EditorSet editors = toLoad.getValue(); + if (!editors.formNode.isScreen1()) { // Screen1 loading is already initialized + if (editors.formNode == null) { + Ode.getInstance().genericWarning(MESSAGES.screenMissingDesigner(project.getProjectName(), toLoad.getKey())); + } else if (editors.blocksNode == null) { + Ode.getInstance().genericWarning(MESSAGES.screenMissingBlocks(project.getProjectName(), toLoad.getKey())); + } else { + addFormEditor(editors.formNode, callback); + addBlocksEditor(editors.blocksNode); + DesignToolbar designToolbar = Ode.getInstance().getDesignToolbar(); + designToolbar.addScreen(projectRootNode.getProjectId(), + toLoad.getKey(), editors.formEditor, editors.blocksEditor); + } + } + Scheduler.get().scheduleDeferred(this); + } + } + }); } // New project loading logic @@ -292,7 +368,7 @@ public void onProjectNodeAdded(Project project, ProjectNode node) { String formName = null; if (node instanceof YoungAndroidFormNode) { if (getFileEditor(node.getFileId()) == null) { - addFormEditor((YoungAndroidFormNode) node); + addFormEditor((YoungAndroidFormNode) node, null); formName = ((YoungAndroidFormNode) node).getFormName(); } } else if (node instanceof YoungAndroidBlocksNode) { @@ -458,9 +534,11 @@ public int compare(String fileId1, String fileId2) { }; } - private void addFormEditor(YoungAndroidFormNode formNode) { + private void addFormEditor(YoungAndroidFormNode formNode, final Runnable callback) { + final long start = System.currentTimeMillis(); final YaFormEditor newFormEditor = new YaFormEditor(this, formNode); final String formName = formNode.getFormName(); + LOG.info("Adding form editor for " + formName); if (editorMap.containsKey(formName)) { // This happens if the blocks editor was already added. editorMap.get(formName).formEditor = newFormEditor; @@ -473,6 +551,7 @@ private void addFormEditor(YoungAndroidFormNode formNode) { final Command afterLoadCommand = new Command() { @Override public void execute() { + LOG.info("Loaded " + formName + ".scm"); int pos = Collections.binarySearch(fileIds, newFormEditor.getFileId(), getFileIdComparator()); if (pos < 0) { @@ -483,12 +562,12 @@ public void execute() { screen1FormLoaded = true; if (readyToShowScreen1()) { LOG.info("YaProjectEditor.addFormEditor.loadFile.execute: switching to screen " - + formName + " for project " + newFormEditor.getProjectId()); + + formName + " for project " + newFormEditor.getProjectId()); Ode.getInstance().getDesignToolbar().switchToScreen(newFormEditor.getProjectId(), formName, DesignToolbar.View.FORM); } } - loadBlocksEditor(formName); + loadBlocksEditor(formName, callback); } }; if (!isScreen1(formName) && !screen1FormLoaded) { @@ -498,6 +577,7 @@ public void execute() { @Override public boolean execute() { if (screen1FormLoaded) { + loadingEditors.add(editorMap.get(formName)); newFormEditor.loadFile(afterLoadCommand); return false; } else { @@ -506,8 +586,10 @@ public boolean execute() { } }, 100); } else { + loadingEditors.add(editorMap.get(formName)); newFormEditor.loadFile(afterLoadCommand); } + LOG.info("Adding form editor for " + formName + " took " + (System.currentTimeMillis() - start) + " ms"); } private boolean readyToShowScreen1() { @@ -515,8 +597,10 @@ private boolean readyToShowScreen1() { } private void addBlocksEditor(YoungAndroidBlocksNode blocksNode) { + final long start = System.currentTimeMillis(); final YaBlocksEditor newBlocksEditor = new YaBlocksEditor(this, blocksNode); final String formName = blocksNode.getFormName(); + LOG.info("Adding blocks editor for " + formName); if (editorMap.containsKey(formName)) { // This happens if the form editor was already added. EditorSet pair = editorMap.get(formName); @@ -527,6 +611,7 @@ private void addBlocksEditor(YoungAndroidBlocksNode blocksNode) { editors.blocksEditor = newBlocksEditor; editorMap.put(formName, editors); } + LOG.info("Adding blocks editor for " + formName + " took " + (System.currentTimeMillis() - start) + " ms"); } private void removeFormEditor(String formName) { diff --git a/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ChecksumedLoadFile.java b/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ChecksumedLoadFile.java index 83144c50f8e..7d18817aeb4 100644 --- a/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ChecksumedLoadFile.java +++ b/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ChecksumedLoadFile.java @@ -1,6 +1,6 @@ // -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved -// Copyright 2011-2014 MIT, All rights reserved +// Copyright 2011-2019 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 @@ -8,7 +8,6 @@ import java.io.Serializable; import java.security.MessageDigest; -import java.io.ObjectStreamClass; /** * A class used to return text files, in particular .bky and .scm files diff --git a/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectNode.java b/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectNode.java index c9367322496..9c510dff7d1 100644 --- a/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectNode.java +++ b/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectNode.java @@ -1,6 +1,6 @@ // -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved -// Copyright 2011-2012 MIT, All rights reserved +// Copyright 2011-2019 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 @@ -243,11 +243,11 @@ public ProjectNode findNode(Class type) { * * @param bucket container to collect found source nodes in */ - protected void findSourceNodes(List bucket) { + protected void findSourceNodes(List bucket) { if (children != null) { for (ProjectNode child : children) { if (child.isSourceNode()) { - bucket.add(child); + bucket.add((SourceNode) child); } child.findSourceNodes(bucket); } diff --git a/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectRootNode.java b/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectRootNode.java index dd0161eec50..ce73f910e55 100644 --- a/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectRootNode.java +++ b/appinventor/appengine/src/com/google/appinventor/shared/rpc/project/ProjectRootNode.java @@ -1,6 +1,6 @@ // -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved -// Copyright 2011-2012 MIT, All rights reserved +// Copyright 2011-2019 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 @@ -51,8 +51,8 @@ public ProjectRootNode(String name, long projectId, String type) { * * @return list of source project nodes */ - public List getAllSourceNodes() { - List sourceNodes = new ArrayList(); + public List getAllSourceNodes() { + List sourceNodes = new ArrayList(); findSourceNodes(sourceNodes); return sourceNodes; } @@ -61,8 +61,8 @@ public List getAllSourceNodes() { * Returns the source node with the given fileId, or null if there is no source node with the * given fileId. */ - public ProjectNode getSourceNode(String fileId) { - for (ProjectNode node : getAllSourceNodes()) { + public SourceNode getSourceNode(String fileId) { + for (SourceNode node : getAllSourceNodes()) { if (node.getFileId().equals(fileId)) { return node; }