BowlerKernel
Vitamins.java
Go to the documentation of this file.
1 package com.neuronrobotics.bowlerstudio.vitamins;
2 
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.HashMap;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Set;
10 
11 import com.neuronrobotics.sdk.common.Log;
12 
13 import eu.mihosoft.vrl.v3d.CSG;
14 import eu.mihosoft.vrl.v3d.STL;
15 import eu.mihosoft.vrl.v3d.parametrics.LengthParameter;
16 import eu.mihosoft.vrl.v3d.parametrics.StringParameter;
17 
18 import com.neuronrobotics.bowlerstudio.BowlerKernel;
19 import com.neuronrobotics.bowlerstudio.IssueReportingExceptionHandler;
20 import com.neuronrobotics.bowlerstudio.scripting.PasswordManager;
21 //import com.neuronrobotics.bowlerstudio.BowlerStudio;
22 import com.neuronrobotics.bowlerstudio.scripting.ScriptingEngine;
23 //import com.neuronrobotics.bowlerstudio.util.FileChangeWatcher;
24 //import com.neuronrobotics.bowlerstudio.util.IFileChangeListener;
25 //import com.neuronrobotics.bowlerstudio.util.FileChangeWatcher;
26 import com.neuronrobotics.bowlerstudio.vitamins.Vitamins;
27 
28 import java.io.InputStream;
29 import java.lang.reflect.Type;
30 import java.net.URISyntaxException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.io.IOUtils;
36 import org.eclipse.jgit.api.errors.CheckoutConflictException;
37 import org.eclipse.jgit.api.errors.GitAPIException;
38 import org.eclipse.jgit.api.errors.InvalidRemoteException;
39 import org.eclipse.jgit.api.errors.NoHeadException;
40 import org.eclipse.jgit.api.errors.TransportException;
41 import org.kohsuke.github.GHIssueState;
42 import org.kohsuke.github.GHPullRequest;
43 import org.kohsuke.github.GHRepository;
44 
45 import com.google.gson.Gson;
46 import com.google.gson.GsonBuilder;
47 import com.google.gson.reflect.TypeToken;
48 
49 public class Vitamins {
50 
51  private static String jsonRootDir = "json/";
52  private static final Map<String, CSG> fileLastLoaded = new HashMap<String, CSG>();
53  private static final Map<String, HashMap<String, HashMap<String, Object>>> databaseSet = new HashMap<String, HashMap<String, HashMap<String, Object>>>();
54  private static final String defaultgitRpoDatabase = "https://github.com/madhephaestus/Hardware-Dimensions.git";
55  private static String gitRpoDatabase = defaultgitRpoDatabase;
56  // Create the type, this tells GSON what datatypes to instantiate when parsing
57  // and saving the json
58  private static Type TT_mapStringString = new TypeToken<HashMap<String, HashMap<String, Object>>>() {
59  }.getType();
60  // chreat the gson object, this is the parsing factory
61  private static Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
62  private static boolean checked;
63  private static HashMap<String,Runnable> changeListeners = new HashMap<String, Runnable>();
64  public static void clear() {
65  System.out.println("Vitamins Database Cleraing, reloading files");
66  for(String keys:databaseSet.keySet()) {
67  HashMap<String, HashMap<String, Object>> data = databaseSet.get(keys);
68  for(String key2:data.keySet()) {
69  HashMap<String, Object> data2 = data.get(key2);
70  data2.clear();
71  }
72  data.clear();
73  }
74  databaseSet.clear();
75  fileLastLoaded.clear();
76  }
77  public static CSG get(File resource) {
78 
79  if (fileLastLoaded.get(resource.getAbsolutePath()) == null) {
80  // forces the first time the files is accessed by the application tou pull an
81  // update
82  try {
83  fileLastLoaded.put(resource.getAbsolutePath(), STL.file(resource.toPath()));
84 // try {
85 // FileChangeWatcher f = FileChangeWatcher.watch(resource);
86 // f.addIFileChangeListener(new IFileChangeListener() {
87 // @Override
88 // public void onFileDelete(File fileThatIsDeleted) {
89 // fileLastLoaded.remove(resource.getAbsolutePath());
90 // f.close();
91 // }
92 //
93 // @Override
94 // public void onFileChange(File fileThatChanged, WatchEvent event) {
95 // fileLastLoaded.remove(resource.getAbsolutePath());
96 // f.close();
97 // }
98 // });
99 // } catch (IOException e) {
100 // // TODO Auto-generated catch block
101 // e.printStackTrace();
102 // }
103  } catch (IOException e) {
104  // TODO Auto-generated catch block
105  e.printStackTrace();
106  }
107  }
108 
109  return fileLastLoaded.get(resource.getAbsolutePath()).clone();
110  }
111 
112  public static CSG get(String type, String id, String purchasingVariant) throws Exception {
113  String key = type + id + purchasingVariant;
114  if (fileLastLoaded.get(key) == null) {
115  PurchasingData purchasData = Purchasing.get(type, id, purchasingVariant);
116  for (String variable : purchasData.getVariantParameters().keySet()) {
117  double data = purchasData.getVariantParameters().get(variable);
118  LengthParameter parameter = new LengthParameter(variable, data,
119  (ArrayList<Double>) Arrays.asList(data, data));
120  parameter.setMM(data);
121  }
122 
123  try {
124  fileLastLoaded.put(key, get(type, id));
125  } catch (Exception e) {
126  e.printStackTrace();
127 
129  clear();
130  return get(type, id);
131  }
132 
133  }
134 
135  CSG vitToGet = fileLastLoaded.get(type + id);
136  // System.err.println("Loading "+vitToGet);
137  return vitToGet;
138  }
139 
140  public static CSG get(String type, String id) throws Exception {
141  return get(type, id, 0);
142  }
143 
144  private static CSG get(String type, String id, int depthGauge) throws Exception {
145  String key = type + id;
146 
147  try {
148  CSG newVitamin = null;
149  Map<String, Object> script = getMeta(type);
150  StringParameter size = new StringParameter(type + " Default", id, Vitamins.listVitaminSizes(type));
151  size.setStrValue(id);
152  Object file = script.get("scriptGit");
153  Object repostring = script.get("scriptFile");
154  Object repo = repostring;
155  if (file != null && repo != null) {
156  ArrayList<Object> servoMeasurments = new ArrayList<Object>();
157  servoMeasurments.add(id);
158  newVitamin = (CSG) ScriptingEngine.gitScriptRun(script.get("scriptGit").toString(), // git location of
159  // the library
160  repostring.toString(), // file to load
161  servoMeasurments);
162  return newVitamin;
163  } else {
164  Log.error(key + " Failed to load from script");
165  return null;
166  }
167  } catch (Exception e) {
168  e.printStackTrace();
170  clear();
171  if (depthGauge < 2) {
172  return get(type, id, depthGauge + 1);
173  } else {
174  return null;
175  }
176  }
177  }
178 
179  public static File getScriptFile(String type) {
180  Map<String, Object> script = getMeta(type);
181 
182  try {
183  return ScriptingEngine.fileFromGit(script.get("scriptGit").toString(), script.get("scriptFile").toString());
184  } catch (InvalidRemoteException e) {
185  // TODO Auto-generated catch block
186  e.printStackTrace();
187  } catch (TransportException e) {
188  // TODO Auto-generated catch block
189  e.printStackTrace();
190  } catch (GitAPIException e) {
191  // TODO Auto-generated catch block
192  e.printStackTrace();
193  } catch (IOException e) {
194  // TODO Auto-generated catch block
195  e.printStackTrace();
196  }
197  return null;
198  }
199 
200  public static Map<String, Object> getMeta(String type) {
201  return getConfigurationRW(type, "meta");
202  }
203 
204  public static void setScript(String type, String git, String file) throws Exception {
205  setParameter(type, "meta", "scriptGit", git);
206  setParameter(type, "meta", "scriptFile", file);
207  }
208 
209  public static Map<String, Object> getConfiguration(String type, String id){
210  return Collections.unmodifiableMap(getConfigurationRW( type, id));
211  }
212  public static void putMeasurment(String type, String size,String measurementName, Object measurmentValue) {
213  getConfigurationRW(type,size).put(measurementName, measurmentValue);
214  }
215  public static Object getMeasurement(String type, String size,String measurementName) {
216  return getConfigurationRW(type,size).get(measurementName);
217  }
218  public static HashMap<String, Object> getConfigurationRW(String type, String id) {
219  HashMap<String, HashMap<String, Object>> database = getDatabase(type);
220  if (database.get(id) == null) {
221  database.put(id, new HashMap<String, Object>());
222  }
223  for(int j=0;j<5;j++) {
224  try {
225  HashMap<String, Object> hashMap = database.get(id);
226  Object[] array = hashMap.keySet().toArray();
227  for (int i=0;i<array.length;i++) {
228  String key = (String)array[i];
229  sanatize(key, hashMap);
230  }
231  return hashMap;
232  }catch (java.util.ConcurrentModificationException ex) {
233  if(j==4) {
235  }else {
236  try {
237  Thread.sleep(5);
238  } catch (InterruptedException e) {
239  // TODO Auto-generated catch block
240  e.printStackTrace();
241  }
242  }
243  }
244  }
245  return new HashMap<String, Object>();
246  }
247 
248  public static String makeJson(String type) {
249  return gson.toJson(getDatabase(type), TT_mapStringString);
250  }
251 
252  public static void saveDatabase(String type) throws Exception {
253 
254  // Save contents and publish them
255  String jsonString = makeJson(type);
256  try {
257  //new Exception().printStackTrace();
258  ScriptingEngine.pushCodeToGit(getGitRepoDatabase(), // git repo, change this if you fork this demo
260  getRootFolder() + type + ".json", // local path to the file in git
261  jsonString, // content of the file
262  "Making changes to "+type+" by "+PasswordManager.getUsername()+"\n\nAuto-save inside com.neuronrobotics.bowlerstudio.vitamins.Vitamins inside bowler-scripting-kernel");// commit message
263  //System.err.println(jsonString);
264  System.out.println("Database saved "+getVitaminFile(type,null,false).getAbsolutePath());
265  } catch (org.eclipse.jgit.api.errors.TransportException ex) {
266  System.out.println("You need to fork " + defaultgitRpoDatabase + " to have permission to save");
267  System.out.println(
268  "You do not have permission to push to this repo, change the GIT repo to your fork with setGitRpoDatabase(String gitRpoDatabase) ");
269  throw ex;
270  }
271 
272  }
273  public static void saveDatabaseForkIfMissing(String type) throws Exception {
274 
275  org.kohsuke.github.GitHub github = PasswordManager.getGithub();
276  GHRepository repo = github.getRepository("madhephaestus/Hardware-Dimensions");
277  try {
278  saveDatabase(type);
279  } catch (org.eclipse.jgit.api.errors.TransportException ex) {
280 
281 
282  GHRepository newRepo = repo.fork();
283 
284  Vitamins.gitRpoDatabase = newRepo.getGitTransportUrl().replaceAll("git://", "https://");
285  saveDatabase(type);
286 
287  }
288  if(PasswordManager.getUsername().contentEquals("madhephaestus"))
289  return;
290  try {
291  GHRepository myrepo = github.getRepository(PasswordManager.getUsername()+"/Hardware-Dimensions");
292  List<GHPullRequest> asList1 = myrepo.queryPullRequests().state(GHIssueState.OPEN).head("madhephaestus:master")
293  .list().asList();
294  Thread.sleep(200);// Some asynchronus delay here, not sure why...
295  if(asList1.size()==0) {
296  try {
297  GHPullRequest request = myrepo.createPullRequest("Update from source",
298  "madhephaestus:master",
299  "master",
300  "## Upstream add vitamins",
301  false, false);
302  if(request!=null) {
303  processSelfPR(request);
304  }
305  }catch(org.kohsuke.github.HttpException ex) {
306  // no commits have been made to master
307  }
308 
309  }else {
310  processSelfPR(asList1.get(0));
311  }
312  String head = PasswordManager.getUsername()+":master";
313  List<GHPullRequest> asList = repo.queryPullRequests()
314  .state(GHIssueState.OPEN)
315  .head(head)
316  .list().asList();
317  if(asList.size()==0) {
318  System.err.println("Creating PR for "+head);
319  GHPullRequest request = repo.createPullRequest("User Added vitamins to "+type,
320  head,
321  "master",
322  "## User added vitamins",
323  true, true);
324  try {
325  BowlerKernel.upenURL(request.getHtmlUrl().toURI());
326  } catch (URISyntaxException e) {
327  // TODO Auto-generated catch block
328  e.printStackTrace();
329  }
330  }else {
331 
332  }
333  }catch(Exception ex) {
334  new IssueReportingExceptionHandler().uncaughtException(Thread.currentThread(),ex);
335  }
336 
337 
338  }
339 
340  private static void processSelfPR(GHPullRequest request) throws IOException {
341  if(request== null)
342  return;
343  try {
344  if (request.getMergeable()) {
345  request.merge("Auto Merging Master");
347  System.out.println("Merged Hardware-Dimensions madhephaestus:master into "+PasswordManager.getUsername()+":master");
348  } else {
349  try {
350  BowlerKernel.upenURL(request.getHtmlUrl().toURI());
351  } catch (URISyntaxException e) {
352  // TODO Auto-generated catch block
353  e.printStackTrace();
354  }
355  }
356  }catch(java.lang.NullPointerException ex) {
357  ex.printStackTrace();
358  }
359  }
360  public static void newVitamin(String type, String id) throws Exception {
361  HashMap<String, HashMap<String, Object>> database = getDatabase(type);
362  if (database.keySet().size() > 0) {
363  String exampleKey = null;
364  for (String key : database.keySet()) {
365  if (!key.contains("meta")) {
366  exampleKey = key;
367  }
368  }
369  if (exampleKey != null) {
370  // this database has examples, load an example
371  Map<String, Object> exampleConfiguration = getConfigurationRW(type, exampleKey);
372  Map<String, Object> newConfig = getConfigurationRW(type, id);
373  for (String key : exampleConfiguration.keySet()) {
374  newConfig.put(key, exampleConfiguration.get(key));
375  }
376  }
377  }
378 
379  getConfigurationRW(type, id);
380  // saveDatabase(type);
381 
382  }
383 
384  public static void setParameter(String type, String id, String parameterName, Object parameter) throws Exception {
385 
386  HashMap<String, Object> config = getConfigurationRW(type, id);
387  config.put(parameterName, parameter);
388  sanatize(parameterName, config);
389 
390  // saveDatabase(type);
391  }
392 
393  private static void sanatize(String parameterName, HashMap<String, Object> config) {
394  Object parameter=config.get(parameterName);
395  try {
396  config.put(parameterName, Double.parseDouble(parameter.toString()));
397  } catch (NumberFormatException ex) {
398  config.put(parameterName, parameter);
399  }
400  }
401 
402  public static HashMap<String, HashMap<String, Object>> getDatabase(String type) {
403  if (databaseSet.get(type) == null) {
404  // we are using the default vitamins configuration
405  // https://github.com/madhephaestus/Hardware-Dimensions.git
406 
407  // create some variables, including our database
408  String jsonString;
409  InputStream inPut = null;
410 
411  // attempt to load the JSON file from the GIt Repo and pars the JSON string
412  File f;
413  try {
414 
415  Runnable onChange=null;
416  if(changeListeners.get(type)==null) {
417  changeListeners.put(type,() -> {
418  // If the file changes, clear the database and load the new data
419  System.out.println("Re-loading "+type);
420  databaseSet.put(type,null);
421  new RuntimeException().printStackTrace();
422  });
423  onChange=changeListeners.get(type);
424  }
425 
426 
427  f = getVitaminFile(type,onChange,true);
428 
429  HashMap<String, HashMap<String, Object>> database;
430  if(f.exists()) {
431 
432  inPut = FileUtils.openInputStream(f);
433 
434  jsonString = IOUtils.toString(inPut);
435  inPut.close();
436  System.out.println("JSON loading Loading "+type+" "+jsonString.length());
437  // perfoem the GSON parse
438  database = gson.fromJson(jsonString, TT_mapStringString);
439  if(database==null)
440  throw new RuntimeException("Database failed to read");
441  }else {
442  database=new HashMap<String, HashMap<String,Object>>();
443  }
444  databaseSet.put(type, database);
445 
446  for (String key : databaseSet.get(type).keySet()) {
447  HashMap<String, Object> conf = database.get(key);
448  for (String confKey : conf.keySet()) {
449  try {
450  double num = Double.parseDouble(conf.get(confKey).toString());
451  conf.put(confKey, num);
452  } catch (NumberFormatException ex) {
453  // ex.printStackTrace();
454  // leave as a string
455  conf.put(confKey, conf.get(confKey).toString());
456  }
457  }
458  }
459 
460  } catch (Exception e) {
461  e.printStackTrace();
462  databaseSet.put(type, new HashMap<String, HashMap<String, Object>>());
463  }
464  }
465  return databaseSet.get(type);
466 
467  }
468 
469  public static File getVitaminFile(String type, Runnable onChange, boolean oneShot)
470  throws InvalidRemoteException, TransportException, GitAPIException, IOException {
471 
472 
473  File f= ScriptingEngine.fileFromGit(getGitRepoDatabase(), // git repo, change this if you fork this demo
474  getRootFolder() + type + ".json"// File from within the Git repo
475  );
476  if(onChange!=null) {
477 // FileChangeWatcher watcher = FileChangeWatcher.watch(f);
478 // watcher.addIFileChangeListener((fileThatChanged, event) -> {
479 // onChange.run();
480 // });
481  }
482  return f;
483  }
484 
485  private static String getRootFolder() {
486  return getJsonRootDir();
487  }
488  public static ArrayList<String> listVitaminActuators() {
489  ArrayList<String> actuators = new ArrayList<String>();
490 
491  for (String vitaminsType : Vitamins.listVitaminTypes()) {
492  if (isActuator( vitaminsType))
493  actuators.add(vitaminsType);
494  }
495  return actuators;
496  }
497  public static ArrayList<String> listVitaminShafts() {
498  ArrayList<String> actuators = new ArrayList<String>();
499  for (String vitaminsType : Vitamins.listVitaminTypes()) {
500  if (isShaft( vitaminsType))
501  actuators.add(vitaminsType);
502  }
503  return actuators;
504  }
505 
506  public static boolean isShaft(String vitaminsType) {
507  Map<String, Object> meta = Vitamins.getMeta(vitaminsType);
508  if (meta != null && meta.containsKey("shaft"))
509  return true;
510  return false;
511  }
512  public static boolean isActuator(String vitaminsType) {
513  Map<String, Object> meta = Vitamins.getMeta(vitaminsType);
514  if (meta != null && meta.containsKey("actuator"))
515  return true;
516  return false;
517  }
518  public static void setIsShaft(String type) {
519  Vitamins.getMeta(type).remove("motor");
520  Vitamins.getMeta(type).put("shaft", "true");
521  }
522 
523  public static void setIsActuator(String type) {
524  Vitamins.getMeta(type).remove("shaft");
525  Vitamins.getMeta(type).put("actuator", "true");
526  }
527  public static ArrayList<String> listVitaminTypes() {
528 
529  ArrayList<String> types = new ArrayList<String>();
530  File folder;
531  try {
532  folder = new File(ScriptingEngine.getRepositoryCloneDirectory(getGitRepoDatabase()).getAbsoluteFile()+"/"+getRootFolder());
533  File[] listOfFiles = folder.listFiles();
534 
535  for (File f : listOfFiles) {
536  if (!f.isDirectory() && f.getName().endsWith(".json")) {
537  types.add(f.getName().substring(0, f.getName().indexOf(".json")));
538  }
539  }
540 
541  } catch (Exception e) {
542  // TODO Auto-generated catch block
543  e.printStackTrace();
544  }
545  Collections.sort(types);
546  return types;
547  }
548 
549  public static ArrayList<String> listVitaminSizes(String type) {
550 
551  ArrayList<String> types = new ArrayList<String>();
552  HashMap<String, HashMap<String, Object>> database = getDatabase(type);
553  Set<String> keys = database.keySet();
554  for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {
555  String s = iterator.next();
556  if (s != null) {
557  if (!s.contains("meta")) {
558  types.add(s);
559  }
560  }
561  }
562  Collections.sort(types);
563  return types;
564  }
565 
566  public static String getGitRepoDatabase() {
567  if (!checked) {
568  checked = true;
569  try {
570  if (PasswordManager.getUsername() != null) {
572  org.kohsuke.github.GitHub github = PasswordManager.getGithub();
573  try {
574  GHRepository repo =github.getRepository(PasswordManager.getLoginID() + "/Hardware-Dimensions" );
575  if(repo!=null) {
576  String myAssets = repo.getGitTransportUrl().replaceAll("git://", "https://");
577  // System.out.println("Using my version of Viamins: "+myAssets);
578  setGitRepoDatabase(myAssets);
579  }else {
580  throw new org.kohsuke.github.GHFileNotFoundException();
581  }
582  }catch(Exception ex) {
584  }
585  }
586  } catch (Exception ex) {
587  new IssueReportingExceptionHandler().uncaughtException(Thread.currentThread(), ex);
588  }
590  }
591  return gitRpoDatabase;
592  }
593 
594  public static void reLoadDatabaseFromFiles() {
595 
597  try {
599  }catch (CheckoutConflictException|NoHeadException e) {
601  try {
603  } catch (Exception e1) {
604  // TODO Auto-generated catch block
605  e1.printStackTrace();
606  }
607  }catch (Exception e) {
608  new IssueReportingExceptionHandler().uncaughtException(Thread.currentThread(), e);
609  }
611 
612  }
613  public static void setGitRepoDatabase(String gitRpoDatabase) {
615  databaseSet.clear();
616  fileLastLoaded.clear();
617 
618  }
619 
620  public static String getJsonRootDir() {
621  return jsonRootDir;
622  }
623 
624  public static void setJsonRootDir(String jsonRootDir) throws IOException {
627  }
628 
629 
630 
631 }
static void pushCodeToGit(String id, String branch, String FileName, String content, String commitMessage)
static File cloneRepo(String remoteURI, String branch)
static Object gitScriptRun(String gitURL, String Filename)
static File fileFromGit(String remoteURI, String fileInRepo)
static void pull(String remoteURI, String branch)
static PurchasingData get(String type, String size, String variant)
static Map< String, Object > getConfiguration(String type, String id)
Definition: Vitamins.java:209
static Object getMeasurement(String type, String size, String measurementName)
Definition: Vitamins.java:215
static ArrayList< String > listVitaminTypes()
Definition: Vitamins.java:527
static ArrayList< String > listVitaminShafts()
Definition: Vitamins.java:497
static boolean isActuator(String vitaminsType)
Definition: Vitamins.java:512
static Map< String, Object > getMeta(String type)
Definition: Vitamins.java:200
static void saveDatabaseForkIfMissing(String type)
Definition: Vitamins.java:273
static final Map< String, CSG > fileLastLoaded
Definition: Vitamins.java:52
static File getVitaminFile(String type, Runnable onChange, boolean oneShot)
Definition: Vitamins.java:469
static void setParameter(String type, String id, String parameterName, Object parameter)
Definition: Vitamins.java:384
static boolean isShaft(String vitaminsType)
Definition: Vitamins.java:506
static ArrayList< String > listVitaminActuators()
Definition: Vitamins.java:488
static void setGitRepoDatabase(String gitRpoDatabase)
Definition: Vitamins.java:613
static HashMap< String, Runnable > changeListeners
Definition: Vitamins.java:63
static void processSelfPR(GHPullRequest request)
Definition: Vitamins.java:340
static ArrayList< String > listVitaminSizes(String type)
Definition: Vitamins.java:549
static void putMeasurment(String type, String size, String measurementName, Object measurmentValue)
Definition: Vitamins.java:212
static HashMap< String, HashMap< String, Object > > getDatabase(String type)
Definition: Vitamins.java:402
static final Map< String, HashMap< String, HashMap< String, Object > > > databaseSet
Definition: Vitamins.java:53
static void sanatize(String parameterName, HashMap< String, Object > config)
Definition: Vitamins.java:393
static HashMap< String, Object > getConfigurationRW(String type, String id)
Definition: Vitamins.java:218
static void setJsonRootDir(String jsonRootDir)
Definition: Vitamins.java:624
static void newVitamin(String type, String id)
Definition: Vitamins.java:360
static void setScript(String type, String git, String file)
Definition: Vitamins.java:204
static void error(String message)
Definition: Log.java:92
static CSG file(URL path)
Definition: STL.java:67