]> git.argeo.org Git - gpl/argeo-slc.git/blob - plugins/org.argeo.slc.ide.ui/src/main/java/org/argeo/slc/ide/ui/launch/osgi/OsgiLaunchHelper.java
347f267235c64cdac0816ba9530ab4d95b582939
[gpl/argeo-slc.git] / plugins / org.argeo.slc.ide.ui / src / main / java / org / argeo / slc / ide / ui / launch / osgi / OsgiLaunchHelper.java
1 package org.argeo.slc.ide.ui.launch.osgi;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Properties;
12 import java.util.Set;
13 import java.util.StringTokenizer;
14 import java.util.TreeMap;
15 import java.util.TreeSet;
16
17 import org.argeo.slc.ide.ui.SlcIdeUiPlugin;
18 import org.eclipse.core.resources.IFile;
19 import org.eclipse.core.resources.IFolder;
20 import org.eclipse.core.resources.IProject;
21 import org.eclipse.core.resources.IResource;
22 import org.eclipse.core.runtime.Assert;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.IPath;
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.core.runtime.Status;
27 import org.eclipse.core.variables.IStringVariableManager;
28 import org.eclipse.core.variables.VariablesPlugin;
29 import org.eclipse.debug.core.ILaunchConfiguration;
30 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
31 import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
32 import org.eclipse.jdt.launching.IVMInstall;
33 import org.eclipse.jdt.launching.IVMInstall2;
34 import org.eclipse.jdt.launching.IVMInstallType;
35 import org.eclipse.jdt.launching.JavaRuntime;
36 import org.eclipse.jface.dialogs.ErrorDialog;
37 import org.eclipse.jface.viewers.ISelection;
38 import org.eclipse.jface.viewers.IStructuredSelection;
39 import org.eclipse.osgi.service.resolver.BundleDescription;
40 import org.eclipse.pde.core.plugin.IPluginModelBase;
41 import org.eclipse.pde.core.plugin.PluginRegistry;
42 import org.eclipse.pde.internal.build.IPDEBuildConstants;
43 import org.eclipse.pde.launching.IPDELauncherConstants;
44 import org.eclipse.swt.widgets.Display;
45 import org.eclipse.swt.widgets.Shell;
46
47 /**
48 * Most of the actual logic is concentrated in this class which manipulates
49 * {@link ILaunchConfigurationWorkingCopy}. Static method are used since the
50 * shortcut and launch configuration classes are already extending PDE classes.
51 */
52 @SuppressWarnings("restriction")
53 public class OsgiLaunchHelper implements OsgiLauncherConstants {
54 private static Boolean debug = true;
55
56 private final static String DEFAULT_DATA_DIR = "data";
57 private final static String DEFAULT_EXEC_DIR = "exec";
58 private final static String DEFAULT_VMARGS = "-Xmx256m";
59 private final static String DEFAULT_PROGRAM_ARGS = "-console";
60
61 /** Sets default values on this configuration. */
62 public static void setDefaults(ILaunchConfigurationWorkingCopy wc,
63 Boolean isEclipse) {
64 try {
65 if (isEclipse) {
66 wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, false);
67 wc.setAttribute(IPDELauncherConstants.USE_PRODUCT, false);
68 }
69
70 wc.setAttribute(ATTR_ADD_JVM_PATHS, false);
71 wc.setAttribute(ATTR_ADDITIONAL_VM_ARGS, DEFAULT_VMARGS);
72 wc.setAttribute(ATTR_ADDITIONAL_PROGRAM_ARGS, DEFAULT_PROGRAM_ARGS);
73
74 // Defaults
75 String originalVmArgs = wc.getAttribute(
76 IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, "");
77 wc.setAttribute(ATTR_DEFAULT_VM_ARGS, originalVmArgs);
78
79 // do NOT use custom features (both must be set)
80 wc.setAttribute(IPDELauncherConstants.USE_CUSTOM_FEATURES, false);
81 wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, true);
82
83 // clear config area by default
84 wc.setAttribute(IPDELauncherConstants.CONFIG_CLEAR_AREA, true);
85 } catch (CoreException e) {
86 Shell shell = Display.getCurrent().getActiveShell();
87 ErrorDialog.openError(shell, "Error",
88 "Cannot execute initalize configuration", e.getStatus());
89 }
90 }
91
92 /** Find the working directory based on this properties file. */
93 public static String findWorkingDirectory(IFile propertiesFile) {
94 try {
95 IProject project = propertiesFile.getProject();
96 IPath parent = propertiesFile.getProjectRelativePath()
97 .removeLastSegments(1);
98 IFolder execFolder = project.getFolder(parent
99 .append(DEFAULT_EXEC_DIR));
100 if (!execFolder.exists())
101 execFolder.create(true, true, null);
102 IFolder launchFolder = project.getFolder(execFolder
103 .getProjectRelativePath().append(
104 extractName(propertiesFile)));
105 if (!launchFolder.exists())
106 launchFolder.create(true, true, null);
107 return "${workspace_loc:"
108 + launchFolder.getFullPath().toString().substring(1) + "}";
109 } catch (Exception e) {
110 e.printStackTrace();
111 throw new RuntimeException("Cannot create working directory", e);
112 }
113 }
114
115 /** Extract the launch configuration name from the properties file. */
116 public static String extractName(IFile propertiesFile) {
117 IPath path = propertiesFile.getFullPath();
118 IPath pathNoExt = path.removeFileExtension();
119 return pathNoExt.segment(pathNoExt.segmentCount() - 1);
120
121 }
122
123 /** Expects properties file to be set as mapped resources */
124 public static void updateLaunchConfiguration(
125 ILaunchConfigurationWorkingCopy wc, Boolean isEclipse) {
126 try {
127 // Finds the properties file and load it
128 IFile propertiesFile = (IFile) wc.getMappedResources()[0];
129 propertiesFile.refreshLocal(IResource.DEPTH_ONE, null);
130 Properties properties = readProperties(propertiesFile);
131
132 // Extract information from the properties file
133 Map<String, Integer> bundlesToStart = new TreeMap<String, Integer>();
134 Map<String, String> systemPropertiesToAppend = new HashMap<String, String>();
135 String applicationId = interpretProperties(properties,
136 bundlesToStart, systemPropertiesToAppend);
137
138 if (applicationId != null)
139 wc.setAttribute(IPDELauncherConstants.APPLICATION,
140 applicationId);
141 else {
142 if (isEclipse)
143 throw new Exception("No application defined,"
144 + " please set the 'eclipse.application' property"
145 + " in the properties file");
146 }
147
148 // Define directories
149 File workingDir = getWorkingDirectory(wc);
150 File dataDir = new File(workingDir, DEFAULT_DATA_DIR);
151
152 // Update the launch configuration accordingly
153 updateLaunchConfiguration(wc, bundlesToStart,
154 systemPropertiesToAppend, dataDir.getAbsolutePath(),
155 isEclipse);
156 } catch (Exception e) {
157 e.printStackTrace();
158 Shell shell = SlcIdeUiPlugin.getDefault().getWorkbench()
159 .getActiveWorkbenchWindow().getShell();
160 // Shell shell= Display.getCurrent().getActiveShell();
161 ErrorDialog.openError(shell, "Error",
162 "Cannot prepare launch configuration",
163 new Status(IStatus.ERROR, SlcIdeUiPlugin.ID,
164 e.getMessage(), e));
165 return;
166 }
167 }
168
169 /**
170 * Actually modifies the launch configuration in order to reflect the
171 * current state read from the properties file and the launch configuration
172 * UI.
173 */
174 protected static void updateLaunchConfiguration(
175 ILaunchConfigurationWorkingCopy wc,
176 Map<String, Integer> bundlesToStart,
177 Map<String, String> systemPropertiesToAppend, String dataDir,
178 Boolean isEclipse) throws CoreException {
179 // Convert bundle lists
180 final String targetBundles;
181 final String wkSpaceBundles;
182 if (wc.getAttribute(ATTR_SYNC_BUNDLES, true)) {
183 StringBuffer tBuf = new StringBuffer();
184 for (IPluginModelBase model : PluginRegistry.getExternalModels()) {
185 tBuf.append(model.getBundleDescription().getSymbolicName());
186 tBuf.append(',');
187 }
188 targetBundles = tBuf.toString();
189 StringBuffer wBuf = new StringBuffer();
190 models: for (IPluginModelBase model : PluginRegistry
191 .getWorkspaceModels()) {
192 if (model.getBundleDescription() == null) {
193 System.err.println("No bundle description for " + model);
194 continue models;
195 }
196 wBuf.append(model.getBundleDescription().getSymbolicName());
197 wBuf.append(',');
198 }
199 wkSpaceBundles = wBuf.toString();
200 } else {
201 targetBundles = wc.getAttribute(targetBundlesAttr(isEclipse), "");
202 wkSpaceBundles = wc.getAttribute(workspaceBundlesAttr(isEclipse),
203 "");
204 }
205 wc.setAttribute(targetBundlesAttr(isEclipse),
206 convertBundleList(bundlesToStart, targetBundles));
207
208 wc.setAttribute(workspaceBundlesAttr(isEclipse),
209 convertBundleList(bundlesToStart, wkSpaceBundles));
210
211 // Update other default information
212 wc.setAttribute(IPDELauncherConstants.DEFAULT_AUTO_START, false);
213
214 // do NOT use custom features (both must be set)
215 wc.setAttribute(IPDELauncherConstants.USE_CUSTOM_FEATURES, false);
216 wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, true);
217
218 // VM arguments (system properties)
219 String defaultVmArgs = wc.getAttribute(
220 OsgiLauncherConstants.ATTR_DEFAULT_VM_ARGS, "");
221 StringBuffer vmArgs = new StringBuffer(defaultVmArgs);
222
223 // Data dir system property
224 if (dataDir != null) {
225 addSysProperty(vmArgs, OsgiLauncherConstants.ARGEO_OSGI_DATA_DIR,
226 dataDir);
227 if (isEclipse) {
228 wc.setAttribute(IPDELauncherConstants.LOCATION, dataDir);
229 }
230 }
231
232 // Add locations of JVMs
233 if (wc.getAttribute(ATTR_ADD_JVM_PATHS, false))
234 addVms(vmArgs);
235
236 // Add other system properties
237 for (String key : systemPropertiesToAppend.keySet())
238 addSysProperty(vmArgs, key, systemPropertiesToAppend.get(key));
239
240 vmArgs.append(" ").append(wc.getAttribute(ATTR_ADDITIONAL_VM_ARGS, ""));
241
242 wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS,
243 vmArgs.toString());
244
245 // Program arguments
246 StringBuffer progArgs = new StringBuffer("");
247 if (dataDir != null) {
248 progArgs.append("-data ");
249 progArgs.append(surroundSpaces(dataDir));
250
251 if (wc.getAttribute(ATTR_CLEAR_DATA_DIRECTORY, false)) {
252 File dataDirFile = new File(dataDir);
253 deleteDir(dataDirFile);
254 dataDirFile.mkdirs();
255 }
256 }
257 String additionalProgramArgs = wc.getAttribute(
258 OsgiLauncherConstants.ATTR_ADDITIONAL_PROGRAM_ARGS, "");
259 progArgs.append(' ').append(additionalProgramArgs);
260 wc.setAttribute(
261 IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
262 progArgs.toString());
263 }
264
265 /** The launch configuration attribute to use for target bundles */
266 protected static String targetBundlesAttr(Boolean isEclipse) {
267 return isEclipse ? IPDELauncherConstants.SELECTED_TARGET_PLUGINS
268 : IPDELauncherConstants.TARGET_BUNDLES;
269 }
270
271 /** The launch configuration attribute to use for workspace bundles */
272 protected static String workspaceBundlesAttr(Boolean isEclipse) {
273 return isEclipse ? IPDELauncherConstants.SELECTED_WORKSPACE_PLUGINS
274 : IPDELauncherConstants.WORKSPACE_BUNDLES;
275 }
276
277 /**
278 * Interprets special properties and register the others as system
279 * properties to append.
280 *
281 * @return the application id defined by
282 * {@link OsgiLauncherConstants#ECLIPSE_APPLICATION}, or null if not
283 * found
284 */
285 protected static String interpretProperties(Properties properties,
286 Map<String, Integer> bundlesToStart,
287 Map<String, String> systemPropertiesToAppend) {
288 // String argeoOsgiStart = properties
289 // .getProperty(OsgiLauncherConstants.ARGEO_OSGI_START);
290 // if (argeoOsgiStart != null) {
291 // StringTokenizer st = new StringTokenizer(argeoOsgiStart, ",");
292 // while (st.hasMoreTokens())
293 // bundlesToStart.add(st.nextToken());
294 // }
295
296 computeBundlesToStart(bundlesToStart, properties, null);
297
298 String applicationId = null;
299 propKeys: for (Object keyObj : properties.keySet()) {
300 String key = keyObj.toString();
301 if (OsgiLauncherConstants.ARGEO_OSGI_START.equals(key))
302 continue propKeys;
303 if (key.startsWith(OsgiLauncherConstants.ARGEO_OSGI_START + "."))
304 continue propKeys;
305 else if (OsgiLauncherConstants.ARGEO_OSGI_BUNDLES.equals(key))
306 continue propKeys;
307 else if (OsgiLauncherConstants.ARGEO_OSGI_LOCATIONS.equals(key))
308 continue propKeys;
309 else if (OsgiLauncherConstants.OSGI_BUNDLES.equals(key))
310 continue propKeys;
311 else if (OsgiLauncherConstants.ECLIPSE_APPLICATION.equals(key))
312 applicationId = properties.getProperty(key);
313 else
314 systemPropertiesToAppend.put(key, properties.getProperty(key));
315 }
316 return applicationId;
317 }
318
319 /** Adds a regular system property. */
320 protected static void addSysProperty(StringBuffer vmArgs, String key,
321 String value) {
322 surroundSpaces(value);
323 String str = "-D" + key + "=" + value;
324 vmArgs.append(' ').append(str);
325 }
326
327 /** Adds JVMS registered in the workspace as special system properties. */
328 protected static void addVms(StringBuffer vmArgs) {
329 addVmSysProperty(vmArgs, "default", JavaRuntime.getDefaultVMInstall());
330 IVMInstallType[] vmTypes = JavaRuntime.getVMInstallTypes();
331 for (IVMInstallType vmType : vmTypes) {
332 for (IVMInstall vmInstall : vmType.getVMInstalls()) {
333 // printVm("", vmInstall);
334 // properties based on name
335 addVmSysProperty(vmArgs, vmInstall.getName(), vmInstall);
336 if (vmInstall instanceof IVMInstall2) {
337 // properties based on version
338 IVMInstall2 vmInstall2 = (IVMInstall2) vmInstall;
339 String version = vmInstall2.getJavaVersion();
340 addVmSysProperty(vmArgs, version, vmInstall);
341
342 List<String> tokens = new ArrayList<String>();
343 StringTokenizer st = new StringTokenizer(version, ".");
344 while (st.hasMoreTokens())
345 tokens.add(st.nextToken());
346 if (tokens.size() >= 2)
347 addVmSysProperty(vmArgs,
348 tokens.get(0) + "." + tokens.get(1), vmInstall);
349 }
350 }
351 }
352
353 }
354
355 /** Adds a special system property pointing to one of the registered JVMs. */
356 protected static void addVmSysProperty(StringBuffer vmArgs, String suffix,
357 IVMInstall vmInstall) {
358 addSysProperty(vmArgs, OsgiLauncherConstants.VMS_PROPERTY_PREFIX + "."
359 + suffix, vmInstall.getInstallLocation().getPath());
360 }
361
362 /** Surround the string with quotes if it contains spaces. */
363 protected static String surroundSpaces(String str) {
364 if (str.indexOf(' ') >= 0)
365 return '\"' + str + '\"';
366 else
367 return str;
368 }
369
370 /**
371 * Reformat the bundle list in order to reflect which bundles have to be
372 * started.
373 */
374 protected static String convertBundleList(
375 Map<String, Integer> bundlesToStart, String original) {
376 if (debug)
377 debug("Original bundle list: " + original);
378
379 StringTokenizer stComa = new StringTokenizer(original, ",");
380 // sort by bundle symbolic name
381 Set<String> bundleIds = new TreeSet<String>();
382 bundles: while (stComa.hasMoreTokens()) {
383
384 String bundleId = stComa.nextToken();
385 if (bundleId.indexOf('*') >= 0)
386 throw new RuntimeException(
387 "Bundle id "
388 + bundleId
389 + " not properly formatted, clean your workspace projects");
390
391 int indexAt = bundleId.indexOf('@');
392 if (indexAt >= 0) {
393 bundleId = bundleId.substring(0, indexAt);
394 }
395
396 // We can now rely on bundleId value
397
398 if (bundleId.endsWith(".source")) {
399 debug("Skip source bundle " + bundleId);
400 continue bundles;
401 } else if (bundleId
402 .equals(IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR)) {
403 // skip simple configurator in order to avoid side-effects
404 continue bundles;
405 }
406 bundleIds.add(bundleId);
407 }
408
409 StringBuffer bufBundles = new StringBuffer(1024);
410 boolean first = true;
411 for (String bundleId : bundleIds) {
412 if (first)
413 first = false;
414 else
415 bufBundles.append(',');
416 boolean modified = false;
417 if (bundlesToStart.containsKey(bundleId)) {
418 Integer startLevel = bundlesToStart.get(bundleId);
419 String startLevelStr = startLevel != null ? startLevel
420 .toString() : "default";
421 bufBundles.append(bundleId).append('@').append(startLevelStr)
422 .append(":true");
423 modified = true;
424 debug("Will start " + bundleId + " at level " + startLevelStr);
425 }
426
427 if (!modified)
428 bufBundles.append(bundleId);
429
430 }
431 String output = bufBundles.toString();
432 return output;
433 }
434
435 // UTILITIES
436 /** Recursively deletes a directory tree. */
437 private static void deleteDir(File dir) {
438 File[] files = dir.listFiles();
439 for (File file : files) {
440 if (file.isDirectory())
441 deleteDir(file);
442 else
443 file.delete();
444 }
445 dir.delete();
446 }
447
448 /** Loads a properties file. */
449 private static Properties readProperties(IFile file) throws CoreException {
450 Properties props = new Properties();
451
452 InputStream in = null;
453 try {
454 in = file.getContents();
455 props.load(in);
456 } catch (Exception e) {
457 throw new CoreException(new Status(IStatus.ERROR,
458 SlcIdeUiPlugin.ID, "Cannot read properties file", e));
459 } finally {
460 if (in != null)
461 try {
462 in.close();
463 } catch (IOException e) {
464 // silent
465 }
466 }
467 return props;
468 }
469
470 /** Determines the start levels for the bundles */
471 private static void computeBundlesToStart(
472 Map<String, Integer> bundlesToStart, Properties properties,
473 Integer defaultStartLevel) {
474
475 // default (and previously, only behaviour)
476 appendBundlesToStart(bundlesToStart, defaultStartLevel,
477 properties.getProperty(OsgiLauncherConstants.ARGEO_OSGI_START,
478 ""));
479
480 // list argeo.osgi.start.* system properties
481 Iterator<Object> keys = properties.keySet().iterator();
482 final String prefix = OsgiLauncherConstants.ARGEO_OSGI_START + ".";
483 while (keys.hasNext()) {
484 String key = (String) keys.next();
485 if (key.startsWith(prefix)) {
486 Integer startLevel;
487 String suffix = key.substring(prefix.length());
488 String[] tokens = suffix.split("\\.");
489 if (tokens.length > 0 && !tokens[0].trim().equals(""))
490 try {
491 // first token is start level
492 startLevel = new Integer(tokens[0]);
493 } catch (NumberFormatException e) {
494 startLevel = defaultStartLevel;
495 }
496 else
497 startLevel = defaultStartLevel;
498
499 // append bundle names
500 String bundleNames = properties.getProperty(key);
501 appendBundlesToStart(bundlesToStart, startLevel, bundleNames);
502 }
503 }
504 }
505
506 /** Append a comma-separated list of bundles to the start levels. */
507 private static void appendBundlesToStart(
508 Map<String, Integer> bundlesToStart, Integer startLevel, String str) {
509 if (str == null || str.trim().equals(""))
510 return;
511
512 String[] bundleNames = str.split(",");
513 for (int i = 0; i < bundleNames.length; i++) {
514 if (bundleNames[i] != null && !bundleNames[i].trim().equals(""))
515 bundlesToStart.put(bundleNames[i], startLevel);
516 }
517 }
518
519 /*
520 * HACKED UTILITIES
521 */
522 // Hacked from
523 // org.eclipse.pde.internal.ui.launcher.LaunchArgumentsHelper.getWorkingDirectory(ILaunchConfiguration)
524 private static File getWorkingDirectory(ILaunchConfiguration configuration)
525 throws CoreException {
526 String working;
527 try {
528 working = configuration.getAttribute(
529 IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,
530 new File(".").getCanonicalPath()); //$NON-NLS-1$
531 } catch (IOException e) {
532 working = "${workspace_loc}/../"; //$NON-NLS-1$
533 }
534 File dir;
535 try {
536 dir = new File(getSubstitutedString(working));
537 } catch (Exception e) {
538 // the directory was most probably deleted
539 IFile propertiesFile = (IFile) configuration.getMappedResources()[0];
540 working = findWorkingDirectory(propertiesFile);
541 dir = new File(getSubstitutedString(working));
542 }
543 if (!dir.exists())
544 dir.mkdirs();
545 return dir;
546 }
547
548 // Hacked from
549 // org.eclipse.pde.internal.ui.launcher.LaunchArgumentsHelper.getSubstitutedString(String)
550 private static String getSubstitutedString(String text)
551 throws CoreException {
552 if (text == null)
553 return ""; //$NON-NLS-1$
554 IStringVariableManager mgr = VariablesPlugin.getDefault()
555 .getStringVariableManager();
556 return mgr.performStringSubstitution(text);
557 }
558
559 /**
560 * Not used anymore, but kept because this routine may be useful in the
561 * future.
562 */
563 protected void addSelectedProjects(StringBuffer name, ISelection selection,
564 List<String> bundlesToStart) {
565 Assert.isNotNull(selection);
566
567 Map<String, IPluginModelBase> bundleProjects = new HashMap<String, IPluginModelBase>();
568 for (IPluginModelBase modelBase : PluginRegistry.getWorkspaceModels()) {
569 IProject bundleProject = modelBase.getUnderlyingResource()
570 .getProject();
571 bundleProjects.put(bundleProject.getName(), modelBase);
572 }
573
574 IStructuredSelection sSelection = (IStructuredSelection) selection;
575 for (Iterator<?> it = sSelection.iterator(); it.hasNext();) {
576 Object obj = it.next();
577 if (obj instanceof IProject) {
578 IProject project = (IProject) obj;
579 if (bundleProjects.containsKey(project.getName())) {
580 IPluginModelBase modelBase = bundleProjects.get(project
581 .getName());
582
583 BundleDescription bundleDescription = null;
584 if (modelBase.isFragmentModel()) {
585 BundleDescription[] hosts = modelBase
586 .getBundleDescription().getHost().getHosts();
587 for (BundleDescription bd : hosts) {
588 if (debug)
589 System.out.println("Host for "
590 + modelBase.getBundleDescription()
591 .getSymbolicName() + ": "
592 + bd.getSymbolicName());
593 bundleDescription = bd;
594 }
595 } else {
596 bundleDescription = modelBase.getBundleDescription();
597 }
598
599 if (bundleDescription != null) {
600 String symbolicName = bundleDescription
601 .getSymbolicName();
602 String bundleName = bundleDescription.getName();
603
604 bundlesToStart.add(symbolicName);
605
606 if (name.length() > 0)
607 name.append(" ");
608 if (bundleName != null)
609 name.append(bundleName);
610 else
611 name.append(symbolicName);
612 }
613 }
614 }
615 }
616 }
617
618 static void debug(Object obj) {
619 if (debug)
620 System.out.println(obj);
621 }
622
623 }