diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 2bdcfedda31..2825de43c60 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -931,14 +931,14 @@ public boolean addFile(File sourceFile) { public void importLibrary(UserLibrary lib) throws IOException { - importLibrary(lib.getSrcFolder()); + importLibrary(lib.getSrcFolder(), lib.getDepSpec()); } /** * Add import statements to the current tab for all of packages inside * the specified jar file. */ - private void importLibrary(File jarPath) throws IOException { + private void importLibrary(File jarPath, String depSpec) throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); @@ -960,7 +960,11 @@ private void importLibrary(File jarPath) throws IOException { for (String aList : list) { buffer.append("#include <"); buffer.append(aList); - buffer.append(">\n"); + buffer.append(">"); + if (depSpec != null) { + buffer.append(" //!Lib \"" + depSpec + "\""); + } + buffer.append("\n"); } buffer.append('\n'); buffer.append(editor.getText()); diff --git a/app/src/processing/app/syntax/SketchTextArea.java b/app/src/processing/app/syntax/SketchTextArea.java index fbd9e0c7c16..cf362313915 100644 --- a/app/src/processing/app/syntax/SketchTextArea.java +++ b/app/src/processing/app/syntax/SketchTextArea.java @@ -39,6 +39,9 @@ import org.fife.ui.rtextarea.RTextAreaUI; import org.fife.ui.rtextarea.RUndoManager; import processing.app.*; +import processing.app.packages.UserLibrary; +import processing.app.packages.LibrarySelection; +import processing.app.preproc.PdePreprocessor; import javax.swing.*; import javax.swing.event.EventListenerList; @@ -396,6 +399,7 @@ public void mouseMoved(MouseEvent e) { t = new TokenImpl(t); } Cursor c2; + String tipText = null; if (t != null && t.isHyperlink()) { if (hoveredOverLinkOffset == -1 || hoveredOverLinkOffset != t.getOffset()) { @@ -432,12 +436,63 @@ public void mouseMoved(MouseEvent e) { c2 = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); hoveredOverLinkOffset = -1; // linkGeneratorResult = null; + + int pos = viewToModel(e.getPoint()); + if (pos > -1) { + int lineNo = -1; + try { + lineNo = getLineOfOffset(pos); + } catch (BadLocationException ex) {} + String line = getTextLine(lineNo); + if (line != null) { + tipText = getImportInfo(line); + } + } } if (getCursor() != c2) { setCursor(c2); // TODO: Repaint just the affected line(s). repaint(); // Link either left or went into. } + setToolTipText(tipText); + } + + private String getImportInfo(String line) { + java.util.List incs = PdePreprocessor.findIncludes(line); + LibrarySelection libSel = null; + if (!incs.isEmpty()) { + // Note: does not have a valid preferSet, so answer may be inaccurate + libSel = BaseNoGui.findLibraryByImport(incs.get(0), new HashSet<>()); + } + String info = null; + if (libSel != null) { + UserLibrary lib = libSel.get(); + info = getLibDescription(lib, incs.get(0)); + for (LibrarySelection recLibSel : lib.getRequiredLibsRec()) { + // TODO: also check import spec of recursive deps + info += "\n* " + getLibDescription(recLibSel.get(), null); + } + } + return info; + } + + private String getLibDescription(UserLibrary lib, String[] importSpec) { + String ver = lib.getVersion(); + if (ver != null) { + ver = " " + ver; + } else { + ver = ""; + } + String folder = ""; + if (lib instanceof UserLibrary) { + folder = ": " + lib.getSrcFolder().toString(); + } + String desc = lib.getName() + " (" + lib.getGlobalName() + ")" + ver + folder; + if (importSpec != null && importSpec[1] != null && + !lib.matchesDepSpecVersion(importSpec[1])) { + desc += "\nWARNING: "+lib.getGlobalName()+" is an incorrect version."; + } + return desc; } private void stopScanningForLinks() { diff --git a/arduino-core/src/cc/arduino/contributions/libraries/ContributedLibrary.java b/arduino-core/src/cc/arduino/contributions/libraries/ContributedLibrary.java index af42a493b89..444a114d1f1 100644 --- a/arduino-core/src/cc/arduino/contributions/libraries/ContributedLibrary.java +++ b/arduino-core/src/cc/arduino/contributions/libraries/ContributedLibrary.java @@ -34,11 +34,14 @@ import java.util.Comparator; import java.util.List; +import java.util.SortedSet; import static processing.app.I18n._; public abstract class ContributedLibrary extends DownloadableContribution { + public abstract String getGlobalName(); + public abstract String getName(); public abstract String getMaintainer(); @@ -142,10 +145,10 @@ public boolean equals(Object obj) { boolean versionEquals = thisVersion == otherVersion || (thisVersion != null && otherVersion != null && thisVersion.equals(otherVersion)); - String thisName = getName(); - String otherName = ((ContributedLibrary) obj).getName(); + String thisName = getGlobalName(); + String otherName = ((ContributedLibrary) obj).getGlobalName(); - boolean nameEquals = thisName == null || otherName == null || thisName.equals(otherName); + boolean nameEquals = thisName != null && otherName != null && thisName.equals(otherName); return versionEquals && nameEquals; } diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index 8aeffcc157b..76b6b15cf4c 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -18,7 +18,9 @@ import processing.app.helpers.filefilters.OnlyFilesWithExtension; import processing.app.legacy.PApplet; import processing.app.packages.LibraryList; +import processing.app.packages.LibrarySelection; import processing.app.packages.UserLibrary; +import processing.app.preproc.PdePreprocessor; import java.io.*; import java.net.URISyntaxException; @@ -850,10 +852,10 @@ static private void createToolPreferences(ContributionsIndexer indexer) { } static public void populateImportToLibraryTable() { - // Populate importToLibraryTable. Each header filename maps to - // a list of libraries. Compiler.java will use only the first - // library on each list. The others are used only to advise - // user of ambiguously matched and duplicate libraries. + // Populate importToLibraryTable. Each header filename maps to a list of + // libraries. Compiler.java will use the dependency specs in the source + // files to decide which library to use from the list. The list is also + // used to advise user of ambiguously matched and duplicate libraries. importToLibraryTable = new HashMap(); for (UserLibrary lib : librariesIndexer.getInstalledLibraries()) { try { @@ -962,6 +964,61 @@ static public void populateImportToLibraryTable() { } } + static public LibrarySelection findLibraryByImport(String[] importSpec, + Set preferSet) { + LibraryList list = importToLibraryTable.get(importSpec[0]); + if (list == null || list.isEmpty()) { + return null; + } + int index = 0; + if (importSpec[1] != null && !importSpec[1].isEmpty()) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i).matchesDepSpec(importSpec[1])) { + index = i; + } + } + } else if (preferSet != null) { + // No dep spec - prefer library already imported in this context + for (int i = 0; i < list.size(); i++) { + if (preferSet.contains(list.get(i))) { + index = i; + } + } + } + // Keep track of libraries already imported in this context + LibrarySelection libSel = new LibrarySelection(list, index); + preferSet.add(libSel.get()); + return libSel; + } + + static public LibrarySelection findLibraryByCode(String code) { + List incs = PdePreprocessor.findIncludes(code); + if (incs.isEmpty()) { + return null; + } + return findLibraryByImport(incs.get(0), null); + } + + static public List findLibrariesByCode(String code, + Set preferSet) + throws IOException { + List libs = new ArrayList<>(); + List incs = PdePreprocessor.findIncludes(code); + for (String[] inc : incs) { + LibrarySelection lib = findLibraryByImport(inc, preferSet); + if (lib != null) { + libs.add(lib); + } + } + return libs; + } + + static public List findLibrariesByCode(File file, + Set preferSet) + throws IOException { + return findLibrariesByCode(FileUtils.readFileToString(file), preferSet); + } + static public void initParameters(String args[]) throws IOException { String preferencesFile = null; diff --git a/arduino-core/src/processing/app/debug/Compiler.java b/arduino-core/src/processing/app/debug/Compiler.java index 5f1bf9ad567..b38a3ab0136 100644 --- a/arduino-core/src/processing/app/debug/Compiler.java +++ b/arduino-core/src/processing/app/debug/Compiler.java @@ -48,6 +48,7 @@ import processing.app.legacy.PApplet; import processing.app.packages.LegacyUserLibrary; import processing.app.packages.UserLibrary; +import processing.app.packages.LibrarySelection; import processing.app.tools.DoubleQuotedArgumentsOnWindowsCommandLine; public class Compiler implements MessageConsumer { @@ -489,12 +490,15 @@ private void adviseDuplicateLibraries() { System.out.println(I18n.format(_("Multiple libraries were found for \"{0}\""), importedDuplicateHeaders.get(i))); boolean first = true; - for (UserLibrary lib : importedDuplicateLibraries.get(i)) { - if (first) { - System.out.println(I18n.format(_(" Used: {0}"), - lib.getInstalledFolder().getPath())); - first = false; - } else { + LibrarySelection libSel = importedDuplicateLibraries.get(i); + + System.out.println(I18n.format(_(" Used: {0}"), + libSel.get().getInstalledFolder().getPath())); + first = false; + + for (int j = 0; j < libSel.getList().size(); j++) { + if (j != libSel.getIndex()) { + UserLibrary lib = libSel.getList().get(j); System.out.println(I18n.format(_(" Not used: {0}"), lib.getInstalledFolder().getPath())); } @@ -608,6 +612,36 @@ private PreferencesMap createBuildPreferences(String _buildPath, return p; } + static public List findAllSources(File sourcePath, boolean recurse) { + List allSources = new ArrayList<>(); + allSources.addAll(findFilesInFolder(sourcePath, "S", recurse)); + allSources.addAll(findFilesInFolder(sourcePath, "c", recurse)); + allSources.addAll(findFilesInFolder(sourcePath, "cpp", recurse)); + allSources.addAll(findFilesInFolder(sourcePath, "h", recurse)); + allSources.addAll(findFilesInFolder(sourcePath, "hh", recurse)); + allSources.addAll(findFilesInFolder(sourcePath, "hpp", recurse)); + return allSources; + } + + static public List findRequiredLibs(File sourcePath, boolean recurse, Set preferSet) { + List files = findAllSources(sourcePath, recurse); + List result = new ArrayList<>(); + for (File file : files) { + List libSels = null; + try { + libSels = BaseNoGui.findLibrariesByCode(file, preferSet); + } catch (IOException e) { + continue; + } + for (LibrarySelection libSel : libSels) { + if (!result.contains(libSel)) { + result.add(libSel); + } + } + } + return result; + } + private List compileFiles(File outputPath, File sourcePath, boolean recurse, List includeFolders) throws RunnerException, PreferencesMapException { @@ -1353,17 +1387,30 @@ public void preprocess(String buildPath, PdePreprocessor preprocessor) throws Ru // grab the imports from the code just preproc'd importedLibraries = new LibraryList(); - importedDuplicateHeaders = new ArrayList(); - importedDuplicateLibraries = new ArrayList(); - for (String item : preprocessor.getExtraImports()) { - LibraryList list = BaseNoGui.importToLibraryTable.get(item); - if (list != null) { - UserLibrary lib = list.peekFirst(); - if (lib != null && !importedLibraries.contains(lib)) { + importedDuplicateHeaders = new ArrayList<>(); + importedDuplicateLibraries = new ArrayList<>(); + Set preferSet = new HashSet<>(); + for (String[] item : preprocessor.getExtraImports()) { + LibrarySelection libSel = BaseNoGui.findLibraryByImport(item, preferSet); + if (libSel != null) { + UserLibrary lib = libSel.get(); + if (!importedLibraries.contains(lib)) { importedLibraries.add(lib); - if (list.size() > 1) { - importedDuplicateHeaders.add(item); - importedDuplicateLibraries.add(list); + if (libSel.getList().size() > 1) { + importedDuplicateHeaders.add(item[0]); + importedDuplicateLibraries.add(libSel); + } + // Add recursive dependencies + for (LibrarySelection libSelRec : lib.getRequiredLibsRec()) { + if (!importedLibraries.contains(libSelRec.get())) { + importedLibraries.add(libSelRec.get()); + // This is probably just confusing. People can examine the + // tooltips if they need this information. + //if (libSelRec.getList().size() > 1) { + // importedDuplicateHeaders.add("UNKNOWN"); // FIXME + // importedDuplicateLibraries.add(libSelRec); + //} + } } } } @@ -1398,7 +1445,7 @@ public void preprocess(String buildPath, PdePreprocessor preprocessor) throws Ru */ private LibraryList importedLibraries; private List importedDuplicateHeaders; - private List importedDuplicateLibraries; + private List importedDuplicateLibraries; /** * Map an error from a set of processed .java files back to its location diff --git a/arduino-core/src/processing/app/packages/LegacyUserLibrary.java b/arduino-core/src/processing/app/packages/LegacyUserLibrary.java index 56a86ccb9c7..e5abc53776c 100644 --- a/arduino-core/src/processing/app/packages/LegacyUserLibrary.java +++ b/arduino-core/src/processing/app/packages/LegacyUserLibrary.java @@ -43,6 +43,7 @@ public static LegacyUserLibrary create(File libFolder) { res.setInstalled(true); res.layout = LibraryLayout.FLAT; res.name = libFolder.getName(); + res.globalName = res.name; res.setTypes(Arrays.asList("Contributed")); res.setCategory("Uncategorized"); return res; diff --git a/arduino-core/src/processing/app/packages/LibrarySelection.java b/arduino-core/src/processing/app/packages/LibrarySelection.java new file mode 100644 index 00000000000..417024ef10b --- /dev/null +++ b/arduino-core/src/processing/app/packages/LibrarySelection.java @@ -0,0 +1,26 @@ +package processing.app.packages; + +public class LibrarySelection { + private LibraryList list; + private int index; + public LibrarySelection(LibraryList list, int index) { + this.list = list; + this.index = index; + } + public UserLibrary get() { + return list.get(index); + } + public LibraryList getList() { + return list; + } + public int getIndex() { + return index; + } + public boolean equals(Object otherObj) { + if (!(otherObj instanceof LibrarySelection)) { + return false; + } + LibrarySelection other = (LibrarySelection) otherObj; + return this.get().equals(other.get()); + } +} diff --git a/arduino-core/src/processing/app/packages/UserLibrary.java b/arduino-core/src/processing/app/packages/UserLibrary.java index ef24022abd9..f3b8dfce12c 100644 --- a/arduino-core/src/processing/app/packages/UserLibrary.java +++ b/arduino-core/src/processing/app/packages/UserLibrary.java @@ -30,18 +30,28 @@ import cc.arduino.contributions.libraries.ContributedLibrary; import cc.arduino.contributions.libraries.ContributedLibraryReference; +import processing.app.packages.LibrarySelection; import processing.app.helpers.FileUtils; import processing.app.helpers.PreferencesMap; +import processing.app.debug.Compiler; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.TreeMap; +import java.util.Map; +import java.util.HashSet; +import java.util.TreeSet; +import java.util.SortedSet; +import java.util.Set; public class UserLibrary extends ContributedLibrary { + protected String globalName; private String name; private String version; private String author; @@ -151,6 +161,12 @@ public static UserLibrary create(File libFolder) throws IOException { } UserLibrary res = new UserLibrary(); + + String globalName = properties.get("global_name"); + if (globalName != null) { + globalName = globalName.trim(); + } + res.setInstalledFolder(libFolder); res.setInstalled(true); res.name = properties.get("name").trim(); @@ -165,9 +181,112 @@ public static UserLibrary create(File libFolder) throws IOException { res.architectures = archs; res.layout = layout; res.declaredTypes = typesList; + res.setGlobalName(globalName); return res; } + /** + * Call this after setting website, author, and name. + */ + public void setGlobalName(String gn) { + globalName = gn; + boolean invalid = false; + if (globalName == null || globalName.equals("")) { + invalid = true; + String rest = website.replaceFirst(".*://", "").replaceFirst("\\.[^/.]*$", ""); + List parts = Arrays.asList(rest.split("/")); + List hostParts = Arrays.asList(parts.get(0).split("\\.")); + Collections.reverse(hostParts); + globalName = String.join(".", hostParts); + if (globalName.endsWith(".www")) { + // Remove www component + globalName = globalName.substring(0, globalName.length() - 4); + } + if (parts.size() > 1) { + // Path parts + globalName += "." + String.join(".", parts.subList(1, parts.size())); + } + if (globalName.startsWith("com.github.")) { + // Better to use the user-only namespace. + globalName = "io.github." + globalName.substring(11); + } + } + if (globalName == null || globalName.equals("")) { + invalid = true; + // Fallback. Note: the global name is used to test for equality, + // so it must have a value. + globalName = author.replace('|', '_') + "|" + name; + } + if (invalid) { + System.err.println("WARNING: global_name not set in library " + name + ". Guessing '" + globalName + "'.\nPlease set this to a suitable Java-style package name, e.g. io.github.myaccount.myproject\nThe name should be set manually to avoid confusion when forking."); + } + } + + @Override + public String getGlobalName() { + return globalName; + } + + public String getDepSpec() { + String depSpec = null; + if (globalName != null) { + depSpec = globalName; + if (version != null) { + depSpec += ":" + version; + } + } + return depSpec; + } + + @Override + public int hashCode() { + return getDepSpec().hashCode(); + } + + public boolean matchesDepSpec(String spec) { + String[] parts = spec.split(":", 2); + return getGlobalName().equals(parts[0]); + } + + public boolean matchesDepSpecVersion(String spec) { + String[] parts = spec.split(":", 2); + if (parts.length == 1) { + // No version spec - accept any version. + return true; + } + if (getVersion() == null) { + // Unlabeled version - fails to match any version spec. + return false; + } + String[] vers = parts[1].split("-"); + if (vers.length == 2) { + return versionsOrdered(vers[0], getVersion()) && + versionsOrdered(getVersion(), vers[1]); + } else if (parts[1].endsWith("+")) { + return versionsOrdered(parts[1].substring(0, parts[1].length() - 1), getVersion()); + } else if (parts[1].endsWith("*")) { + return getVersion().startsWith(parts[1].substring(0, parts[1].length() - 1)); + } else { + return parts[1].equals(getVersion()); + } + } + + static private boolean versionsOrdered(String ver1, String ver2) { + String[] c1 = ver1.split("\\."); + String[] c2 = ver2.split("\\."); + int i; + for (i = 0; i < c1.length && i < c2.length; i++) { + int n1 = Integer.parseInt(c1[i]); + int n2 = Integer.parseInt(c2[i]); + if (n1 > n2) { + return false; + } else if (n1 < n2) { + return true; + } + } + return c1.length <= c2.length; + } + @Override public String getName() { return name; @@ -261,6 +380,88 @@ public List getRequires() { return null; } + private Map lastUpdateTimes = new TreeMap(); + + private List requiredLibs = null; + private List requiredLibsRec = null; + + private boolean changedSinceLastUpdate(int idx) { + List files = Compiler.findAllSources(getSrcFolder(), useRecursion()); + // Important: must update timestamps from ALL files before returning. + boolean changed = false; + for (File file : files) { + String fname = file.toString(); + long modTime = file.lastModified(); + if (!lastUpdateTimes.containsKey(fname)) { + long[] times = new long[2]; + times[idx] = modTime; + lastUpdateTimes.put(fname, times); + changed = true; + } else if (lastUpdateTimes.get(fname)[idx] < modTime) { + lastUpdateTimes.get(fname)[idx] = modTime; + changed = true; + } + } + return changed; + } + + public boolean changedSinceLastUpdateRec(int idx, SortedSet visited) { + // Prevent infinite recursion. + if (visited.contains(getDepSpec())) { + return false; + } + visited.add(getDepSpec()); + + if (changedSinceLastUpdate(idx)) { + return true; + } + for (LibrarySelection libSel : getRequiredLibs()) { + if (libSel.get().changedSinceLastUpdateRec(idx, visited)) { + return true; + } + } + return false; + } + + public List getRequiredLibs() { + if (requiredLibs == null || changedSinceLastUpdate(0)) { + // Note: the "recursion" in useRecursion() refers to a strategy for + // finding files within an individual project + Set preferSet = new HashSet<>(); + preferSet.add(this); + requiredLibs = Compiler.findRequiredLibs(getSrcFolder(), useRecursion(), preferSet); + requiredLibs.remove(this); + } + return requiredLibs; + } + + public List getRequiredLibsRec() { + return getRequiredLibsRec(new TreeSet<>()); + } + + public List getRequiredLibsRec(SortedSet visited) { + // Prevent infinite recursion. + if (visited.contains(getDepSpec())) { + return new ArrayList<>(); + } + visited.add(getDepSpec()); + + if (requiredLibsRec == null || changedSinceLastUpdateRec(1, new TreeSet<>(visited))) { + requiredLibsRec = new ArrayList<>(); + for (LibrarySelection libSel : getRequiredLibs()) { + if (!requiredLibsRec.contains(libSel) && libSel.get() != this) { + requiredLibsRec.add(libSel); + for (LibrarySelection libSelRec : libSel.get().getRequiredLibsRec(visited)) { + if (!requiredLibsRec.contains(libSelRec) && libSelRec.get() != this) { + requiredLibsRec.add(libSelRec); + } + } + } + } + } + return requiredLibsRec; + } + public List getDeclaredTypes() { return declaredTypes; } @@ -289,6 +490,7 @@ public boolean useRecursion() { @Override public String toString() { String res = "Library: " + name + "\n"; + res += " (global_name=" + globalName + ")\n"; res += " (version=" + version + ")\n"; res += " (author=" + author + ")\n"; res += " (maintainer=" + maintainer + ")\n"; diff --git a/arduino-core/src/processing/app/preproc/PdePreprocessor.java b/arduino-core/src/processing/app/preproc/PdePreprocessor.java index dc30922a92e..0a8b56034d7 100644 --- a/arduino-core/src/processing/app/preproc/PdePreprocessor.java +++ b/arduino-core/src/processing/app/preproc/PdePreprocessor.java @@ -43,7 +43,7 @@ Processing version Copyright (c) 2004-05 Ben Fry and Casey Reas */ public class PdePreprocessor { - private static final String IMPORT_REGEX = "^\\s*#include\\s*[<\"](\\S+)[\">]"; + private static final String IMPORT_REGEX = "^\\s*#include\\s*[<\"](\\S+)[\">](?:[ \\t]*//!Lib[ \\t]+[\"']([^\"']*)[\"'])?"; // stores number of built user-defined function prototypes public int prototypeCount = 0; @@ -58,7 +58,7 @@ public class PdePreprocessor { // these ones have the .* at the end, since a class name might be at the end // instead of .* which would make trouble other classes using this can lop // off the . and anything after it to produce a package name consistently. - List programImports; + List programImports; // imports just from the code folder, treated differently // than the others, since the imports are auto-generated. @@ -85,6 +85,12 @@ public int writePrefix(String program) // http://dev.processing.org/bugs/show_bug.cgi?id=5 program += "\n"; + //String importRegexp = "(?:^|\\s|;)(import\\s+)(\\S+)(\\s*;)"; + + // This must come before scrubbing the comments, so that we get the + // dependency specs (//!Lib comments) + programImports = findIncludes(program); + // if the program ends with an unterminated multi-line comment, // an OutOfMemoryError or NullPointerException will happen. // again, not gonna bother tracking this down, but here's a hack. @@ -96,15 +102,6 @@ public int writePrefix(String program) program = substituteUnicode(program); } - //String importRegexp = "(?:^|\\s|;)(import\\s+)(\\S+)(\\s*;)"; - programImports = new ArrayList(); - - String[][] pieces = PApplet.matchAll(program, IMPORT_REGEX); - - if (pieces != null) - for (int i = 0; i < pieces.length; i++) - programImports.add(pieces[i][1]); // the package name - codeFolderImports = new ArrayList(); // if (codeFolderPackages != null) { // for (String item : codeFolderPackages) { @@ -123,15 +120,16 @@ public int writePrefix(String program) return headerCount + prototypeCount; } - public static List findIncludes(String code){ + public static List findIncludes(String code){ String[][] pieces = PApplet.matchAll(code, IMPORT_REGEX); - ArrayList programImports = new ArrayList(); + ArrayList programImports = new ArrayList<>(); if (pieces != null) for (int i = 0; i < pieces.length; i++) - programImports.add(pieces[i][1]); // the package name + programImports.add(new String[] { pieces[i][1], pieces[i][2] }); + // the include file name, and the dependency spec (if any) return programImports; } @@ -213,7 +211,7 @@ protected void writeProgram(PrintStream out, String program, List protot protected void writeFooter(PrintStream out) throws java.lang.Exception {} - public List getExtraImports() { + public List getExtraImports() { return programImports; }