< prev index next >
src/jdk.dev/share/classes/jdk/tools/jimage/JImageTask.java
Print this page
*** 1,7 ****
/*
! * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
--- 1,7 ----
/*
! * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
*** 23,167 ****
* questions.
*/
package jdk.tools.jimage;
- import java.io.BufferedOutputStream;
- import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
- import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
! import java.text.MessageFormat;
! import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
- import java.util.Locale;
- import java.util.MissingResourceException;
- import java.util.ResourceBundle;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
import jdk.internal.jimage.BasicImageReader;
- import jdk.internal.jimage.BasicImageWriter;
import jdk.internal.jimage.ImageHeader;
import jdk.internal.jimage.ImageLocation;
! import jdk.internal.jimage.PackageModuleMap;
class JImageTask {
- static class BadArgs extends Exception {
- static final long serialVersionUID = 8765093759964640723L; // ## re-generate
- final String key;
- final Object[] args;
- boolean showUsage;
! BadArgs(String key, Object... args) {
! super(JImageTask.getMessage(key, args));
! this.key = key;
! this.args = args;
! }
!
! BadArgs showUsage(boolean b) {
! showUsage = b;
! return this;
! }
! }
!
! static abstract class Option {
! final boolean hasArg;
! final String[] aliases;
!
! Option(boolean hasArg, String... aliases) {
! this.hasArg = hasArg;
! this.aliases = aliases;
! }
!
! boolean isHidden() {
! return false;
! }
!
! boolean matches(String opt) {
! for (String a : aliases) {
! if (a.equals(opt)) {
! return true;
! } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
! return true;
! }
! }
! return false;
! }
!
! boolean ignoreRest() {
! return false;
! }
!
! abstract void process(JImageTask task, String opt, String arg) throws BadArgs;
! }
!
! static abstract class HiddenOption extends Option {
! HiddenOption(boolean hasArg, String... aliases) {
! super(hasArg, aliases);
! }
!
! @Override
! boolean isHidden() {
! return true;
! }
! }
!
! static Option[] recognizedOptions = {
! new Option(true, "--dir") {
@Override
! void process(JImageTask task, String opt, String arg) throws BadArgs {
task.options.directory = arg;
}
},
! new HiddenOption(false, "--fullversion") {
@Override
! void process(JImageTask task, String opt, String arg) {
task.options.fullVersion = true;
}
},
! new Option(false, "--help") {
@Override
! void process(JImageTask task, String opt, String arg) {
task.options.help = true;
}
},
! new Option(false, "--verbose") {
@Override
! void process(JImageTask task, String opt, String arg) throws BadArgs {
task.options.verbose = true;
}
},
! new Option(false, "--version") {
@Override
! void process(JImageTask task, String opt, String arg) {
task.options.version = true;
}
},
};
! static class Options {
Task task = Task.LIST;
String directory = ".";
boolean fullVersion;
boolean help;
boolean verbose;
boolean version;
List<File> jimages = new LinkedList<>();
}
private static final String PROGNAME = "jimage";
! private final Options options = new Options();
enum Task {
- RECREATE,
EXTRACT,
INFO,
LIST,
VERIFY
};
private String pad(String string, int width, boolean justifyRight) {
int length = string.length();
--- 23,124 ----
* questions.
*/
package jdk.tools.jimage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+ import java.nio.ByteBuffer;
+ import java.nio.ByteOrder;
+ import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
! import static java.nio.file.StandardOpenOption.READ;
! import static java.nio.file.StandardOpenOption.WRITE;
import java.util.LinkedList;
import java.util.List;
import jdk.internal.jimage.BasicImageReader;
import jdk.internal.jimage.ImageHeader;
+ import static jdk.internal.jimage.ImageHeader.MAGIC;
+ import static jdk.internal.jimage.ImageHeader.MAJOR_VERSION;
+ import static jdk.internal.jimage.ImageHeader.MINOR_VERSION;
import jdk.internal.jimage.ImageLocation;
! import jdk.internal.jimage.ImageModuleData;
! import jdk.internal.jimage.ImageResourcesTree;
! import jdk.tools.jimage.TaskHelper.BadArgs;
! import jdk.tools.jimage.TaskHelper.HiddenOption;
! import jdk.tools.jimage.TaskHelper.Option;
! import jdk.tools.jimage.TaskHelper.OptionsHelper;
class JImageTask {
! static final Option<?>[] recognizedOptions = {
! new Option<JImageTask>(true, "--dir") {
@Override
! protected void process(JImageTask task, String opt, String arg) throws BadArgs {
task.options.directory = arg;
}
},
! new HiddenOption<JImageTask>(false, "--fullversion") {
@Override
! protected void process(JImageTask task, String opt, String arg) {
task.options.fullVersion = true;
}
},
! new Option<JImageTask>(false, "--help") {
@Override
! protected void process(JImageTask task, String opt, String arg) {
task.options.help = true;
}
},
!
! new Option<JImageTask>(true, "--flags") {
! @Override
! protected void process(JImageTask task, String opt, String arg) {
! task.options.flags = arg;
! }
! },
!
! new Option<JImageTask>(false, "--verbose") {
@Override
! protected void process(JImageTask task, String opt, String arg) throws BadArgs {
task.options.verbose = true;
}
},
! new Option<JImageTask>(false, "--version") {
@Override
! protected void process(JImageTask task, String opt, String arg) {
task.options.version = true;
}
},
};
+ private static final TaskHelper taskHelper
+ = new TaskHelper("jdk.tools.jimage.resources.jimage");
+ private static final OptionsHelper<JImageTask> optionsHelper
+ = taskHelper.newOptionsHelper(JImageTask.class, recognizedOptions);
! static class OptionsValues {
Task task = Task.LIST;
String directory = ".";
boolean fullVersion;
boolean help;
+ String flags;
boolean verbose;
boolean version;
List<File> jimages = new LinkedList<>();
}
private static final String PROGNAME = "jimage";
! private final OptionsValues options = new OptionsValues();
enum Task {
EXTRACT,
INFO,
LIST,
+ RECREATE,
+ SET,
VERIFY
};
private String pad(String string, int width, boolean justifyRight) {
int length = string.length();
*** 208,336 ****
private static final int EXIT_SYSERR = 3; // System error or resource exhaustion.
private static final int EXIT_ABNORMAL = 4; // Terminated abnormally.
int run(String[] args) {
if (log == null) {
! log = new PrintWriter(System.out);
}
try {
! handleOptions(args);
if (options.help) {
! showHelp();
}
if (options.version || options.fullVersion) {
! showVersion(options.fullVersion);
}
boolean ok = run();
return ok ? EXIT_OK : EXIT_ERROR;
} catch (BadArgs e) {
! reportError(e.key, e.args);
if (e.showUsage) {
! log.println(getMessage("main.usage.summary", PROGNAME));
}
return EXIT_CMDERR;
} catch (Exception x) {
x.printStackTrace();
return EXIT_ABNORMAL;
} finally {
log.flush();
}
}
- static final String MODULES_ENTRY = PackageModuleMap.MODULES_ENTRY;
- static final String PACKAGES_ENTRY = "/" + PackageModuleMap.PACKAGES_ENTRY;
-
private void recreate() throws IOException, BadArgs {
File directory = new File(options.directory);
- Path dirPath = directory.toPath();
- int chop = dirPath.toString().length() + 1;
-
if (!directory.isDirectory()) {
! throw new BadArgs("err.not.a.dir", directory.getAbsolutePath());
}
!
if (options.jimages.isEmpty()) {
! throw new BadArgs("err.jimage.not.specified");
} else if (options.jimages.size() != 1) {
! throw new BadArgs("err.only.one.jimage");
! }
!
! File jimage = options.jimages.get(0);
! final List<File> files = new ArrayList<>();
! final BasicImageWriter writer = new BasicImageWriter();
! final Long longZero = 0L;
!
! // Note: code sensitive to Netbeans parser crashing.
! long total = Files.walk(dirPath).reduce(longZero, (Long offset, Path path) -> {
! long size = 0;
! String pathString = path.toString();
!
! if (pathString.length() < chop || pathString.startsWith(".")) {
! return 0L;
}
! File file = path.toFile();
!
! if (file.isFile()) {
! String name = pathString.substring(chop).replace(File.separatorChar, '/');
!
! if (options.verbose) {
! log.println(name);
! }
! if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) {
! try {
! try (Stream<String> lines = Files.lines(path)) {
! size = lines.peek(s -> writer.addString(s)).count() * 4;
! }
! } catch (IOException ex) {
! // Caught again when writing file.
! size = 0;
! }
} else {
! size = file.length();
}
-
- writer.addLocation(name, offset, 0L, size);
- files.add(file);
- }
-
- return offset + size;
- },
- (Long offsetL, Long offsetR) -> { return longZero; } );
-
- if (jimage.createNewFile()) {
- try (OutputStream os = Files.newOutputStream(jimage.toPath());
- BufferedOutputStream bos = new BufferedOutputStream(os);
- DataOutputStream out = new DataOutputStream(bos)) {
-
- byte[] index = writer.getBytes();
- out.write(index, 0, index.length);
-
- for (File file : files) {
- try {
- Path path = file.toPath();
- String name = path.toString().replace(File.separatorChar, '/');
-
- if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) {
- for (String line: Files.readAllLines(path)) {
- int off = writer.addString(line);
- out.writeInt(off);
- }
- } else {
- Files.copy(path, out);
- }
- } catch (IOException ex) {
- throw new BadArgs("err.cannot.read.file", file.getName());
- }
- }
- }
- } else {
- throw new BadArgs("err.jimage.already.exists", jimage.getName());
- }
-
}
private void title(File file, BasicImageReader reader) {
log.println("jimage: " + file.getName());
}
--- 165,227 ----
private static final int EXIT_SYSERR = 3; // System error or resource exhaustion.
private static final int EXIT_ABNORMAL = 4; // Terminated abnormally.
int run(String[] args) {
if (log == null) {
! setLog(new PrintWriter(System.out));
}
try {
! List<String> unhandled = optionsHelper.handleOptions(this, args);
! if(!unhandled.isEmpty()) {
! options.task = Enum.valueOf(Task.class, unhandled.get(0).toUpperCase());
! for(int i = 1; i < unhandled.size(); i++) {
! options.jimages.add(new File(unhandled.get(i)));
! }
! }
if (options.help) {
! optionsHelper.showHelp(PROGNAME, "recreate only options:");
}
if (options.version || options.fullVersion) {
! taskHelper.showVersion(options.fullVersion);
}
boolean ok = run();
return ok ? EXIT_OK : EXIT_ERROR;
} catch (BadArgs e) {
! taskHelper.reportError(e.key, e.args);
if (e.showUsage) {
! log.println(taskHelper.getMessage("main.usage.summary", PROGNAME));
}
return EXIT_CMDERR;
} catch (Exception x) {
x.printStackTrace();
return EXIT_ABNORMAL;
} finally {
log.flush();
}
}
private void recreate() throws IOException, BadArgs {
File directory = new File(options.directory);
if (!directory.isDirectory()) {
! throw taskHelper.newBadArgs("err.not.a.dir", directory.getAbsolutePath());
}
! Path dirPath = directory.toPath();
if (options.jimages.isEmpty()) {
! throw taskHelper.newBadArgs("err.jimage.not.specified");
} else if (options.jimages.size() != 1) {
! throw taskHelper.newBadArgs("err.only.one.jimage");
}
! Path jimage = options.jimages.get(0).toPath();
! if (jimage.toFile().createNewFile()) {
! ExtractedImage img = new ExtractedImage(dirPath, log, options.verbose);
! img.recreateJImage(jimage);
} else {
! throw taskHelper.newBadArgs("err.jimage.already.exists", jimage.getFileName());
}
}
private void title(File file, BasicImageReader reader) {
log.println("jimage: " + file.getName());
}
*** 349,384 ****
private interface JImageAction {
public void apply(File file, BasicImageReader reader) throws IOException, BadArgs;
}
private interface ResourceAction {
! public void apply(BasicImageReader reader, String name, ImageLocation location) throws IOException, BadArgs;
}
! private void extract(BasicImageReader reader, String name, ImageLocation location) throws IOException, BadArgs {
File directory = new File(options.directory);
byte[] bytes = reader.getResource(location);
File resource = new File(directory, name);
File parent = resource.getParentFile();
if (parent.exists()) {
if (!parent.isDirectory()) {
! throw new BadArgs("err.cannot.create.dir", parent.getAbsolutePath());
}
} else if (!parent.mkdirs()) {
! throw new BadArgs("err.cannot.create.dir", parent.getAbsolutePath());
}
! if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) {
! List<String> names = reader.getNames(bytes);
! Files.write(resource.toPath(), names);
} else {
Files.write(resource.toPath(), bytes);
}
}
- private static final int NAME_WIDTH = 40;
private static final int NUMBER_WIDTH = 12;
private static final int OFFSET_WIDTH = NUMBER_WIDTH;
private static final int SIZE_WIDTH = NUMBER_WIDTH;
private static final int COMPRESSEDSIZE_WIDTH = NUMBER_WIDTH;
--- 240,279 ----
private interface JImageAction {
public void apply(File file, BasicImageReader reader) throws IOException, BadArgs;
}
private interface ResourceAction {
! public void apply(BasicImageReader reader, String name,
! ImageLocation location) throws IOException, BadArgs;
}
! private void extract(BasicImageReader reader, String name,
! ImageLocation location) throws IOException, BadArgs {
File directory = new File(options.directory);
byte[] bytes = reader.getResource(location);
File resource = new File(directory, name);
File parent = resource.getParentFile();
if (parent.exists()) {
if (!parent.isDirectory()) {
! throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath());
}
} else if (!parent.mkdirs()) {
! throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath());
}
! if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) {
! ImageModuleData imageModuleData = new ImageModuleData(reader, bytes);
! List<String> lines = imageModuleData.fromModulePackages();
! Files.write(resource.toPath(), lines);
} else {
+ if (!ImageResourcesTree.isTreeInfoResource(name)) {
Files.write(resource.toPath(), bytes);
}
}
+ }
private static final int NUMBER_WIDTH = 12;
private static final int OFFSET_WIDTH = NUMBER_WIDTH;
private static final int SIZE_WIDTH = NUMBER_WIDTH;
private static final int COMPRESSEDSIZE_WIDTH = NUMBER_WIDTH;
*** 395,410 ****
} else {
log.println(entry);
}
}
! private void info(File file, BasicImageReader reader) {
ImageHeader header = reader.getHeader();
log.println(" Major Version: " + header.getMajorVersion());
log.println(" Minor Version: " + header.getMinorVersion());
! log.println(" Location Count: " + header.getLocationCount());
log.println(" Offsets Size: " + header.getOffsetsSize());
log.println(" Redirects Size: " + header.getRedirectSize());
log.println(" Locations Size: " + header.getLocationsSize());
log.println(" Strings Size: " + header.getStringsSize());
log.println(" Index Size: " + header.getIndexSize());
--- 290,307 ----
} else {
log.println(entry);
}
}
! private void info(File file, BasicImageReader reader) throws IOException {
ImageHeader header = reader.getHeader();
log.println(" Major Version: " + header.getMajorVersion());
log.println(" Minor Version: " + header.getMinorVersion());
! log.println(" Flags: " + Integer.toHexString(header.getMinorVersion()));
! log.println(" Resource Count: " + header.getResourceCount());
! log.println(" Table Length: " + header.getTableLength());
log.println(" Offsets Size: " + header.getOffsetsSize());
log.println(" Redirects Size: " + header.getRedirectSize());
log.println(" Locations Size: " + header.getLocationsSize());
log.println(" Strings Size: " + header.getStringsSize());
log.println(" Index Size: " + header.getIndexSize());
*** 412,430 ****
private void list(BasicImageReader reader, String name, ImageLocation location) {
print(reader, name);
}
! void verify(BasicImageReader reader, String name, ImageLocation location) {
! if (name.endsWith(".class")) {
! byte[] bytes;
try {
! bytes = reader.getResource(location);
} catch (IOException ex) {
! log.println(ex);
! bytes = null;
}
if (bytes == null || bytes.length <= 4 ||
(bytes[0] & 0xFF) != 0xCA ||
(bytes[1] & 0xFF) != 0xFE ||
(bytes[2] & 0xFF) != 0xBA ||
--- 309,350 ----
private void list(BasicImageReader reader, String name, ImageLocation location) {
print(reader, name);
}
! void set(File file, BasicImageReader reader) throws BadArgs {
try {
! ImageHeader oldHeader = reader.getHeader();
!
! int value = 0;
! try {
! value = Integer.valueOf(options.flags);
! } catch (NumberFormatException ex) {
! throw taskHelper.newBadArgs("err.flags.not.int", options.flags);
! }
!
! ImageHeader newHeader = new ImageHeader(MAGIC, MAJOR_VERSION, MINOR_VERSION,
! value,
! oldHeader.getResourceCount(), oldHeader.getTableLength(),
! oldHeader.getLocationsSize(), oldHeader.getStringsSize());
!
! ByteBuffer buffer = ByteBuffer.allocate(ImageHeader.getHeaderSize());
! buffer.order(ByteOrder.nativeOrder());
! newHeader.writeTo(buffer);
! buffer.rewind();
!
! try (FileChannel channel = FileChannel.open(file.toPath(), READ, WRITE)) {
! channel.write(buffer, 0);
! }
} catch (IOException ex) {
! throw taskHelper.newBadArgs("err.cannot.update.file", file.getName());
}
+ }
+
+ void verify(BasicImageReader reader, String name, ImageLocation location) {
+ if (name.endsWith(".class")) {
+ byte[] bytes = reader.getResource(location);
if (bytes == null || bytes.length <= 4 ||
(bytes[0] & 0xFF) != 0xCA ||
(bytes[1] & 0xFF) != 0xFE ||
(bytes[2] & 0xFF) != 0xBA ||
*** 433,596 ****
print(reader, name);
}
}
}
! private void iterate(JImageAction jimageAction, ResourceAction resourceAction) throws IOException, BadArgs {
for (File file : options.jimages) {
if (!file.exists() || !file.isFile()) {
! throw new BadArgs("err.not.a.jimage", file.getName());
}
String path = file.getCanonicalPath();
BasicImageReader reader = BasicImageReader.open(path);
if (jimageAction != null) {
jimageAction.apply(file, reader);
}
if (resourceAction != null) {
! String[] entryNames = reader.getEntryNames(true);
for (String name : entryNames) {
ImageLocation location = reader.findLocation(name);
resourceAction.apply(reader, name, location);
}
}
}
}
private boolean run() throws IOException, BadArgs {
switch (options.task) {
- case RECREATE:
- recreate();
- break;
case EXTRACT:
iterate(null, this::extract);
break;
case INFO:
iterate(this::info, null);
break;
case LIST:
iterate(this::listTitle, this::list);
break;
case VERIFY:
iterate(this::title, this::verify);
break;
default:
! throw new BadArgs("err.invalid.task", options.task.name()).showUsage(true);
}
return true;
}
private PrintWriter log;
void setLog(PrintWriter out) {
log = out;
! }
! public void handleOptions(String[] args) throws BadArgs {
! // process options
! int first = 0;
!
! if (args.length == 0) {
! return;
! }
!
! String arg = args[first];
!
! if (!arg.startsWith("-")) {
! try {
! options.task = Enum.valueOf(Task.class, arg.toUpperCase());
! first++;
! } catch (IllegalArgumentException e) {
! throw new BadArgs("err.invalid.task", arg).showUsage(true);
! }
! }
!
! for (int i = first; i < args.length; i++) {
! arg = args[i];
!
! if (arg.charAt(0) == '-') {
! Option option = getOption(arg);
! String param = null;
!
! if (option.hasArg) {
! if (arg.startsWith("--") && arg.indexOf('=') > 0) {
! param = arg.substring(arg.indexOf('=') + 1, arg.length());
! } else if (i + 1 < args.length) {
! param = args[++i];
! }
!
! if (param == null || param.isEmpty() || param.charAt(0) == '-') {
! throw new BadArgs("err.missing.arg", arg).showUsage(true);
! }
! }
!
! option.process(this, arg, param);
!
! if (option.ignoreRest()) {
! i = args.length;
! }
! } else {
! File file = new File(arg);
! options.jimages.add(file);
! }
! }
! }
!
! private Option getOption(String name) throws BadArgs {
! for (Option o : recognizedOptions) {
! if (o.matches(name)) {
! return o;
! }
! }
! throw new BadArgs("err.unknown.option", name).showUsage(true);
! }
!
! private void reportError(String key, Object... args) {
! log.println(getMessage("error.prefix") + " " + getMessage(key, args));
! }
!
! private void warning(String key, Object... args) {
! log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
! }
!
! private void showHelp() {
! log.println(getMessage("main.usage", PROGNAME));
! for (Option o : recognizedOptions) {
! String name = o.aliases[0].substring(1); // there must always be at least one name
! name = name.charAt(0) == '-' ? name.substring(1) : name;
! if (o.isHidden() || name.equals("h")) {
! continue;
! }
! log.println(getMessage("main.opt." + name));
! }
! }
!
! private void showVersion(boolean full) {
! log.println(version(full ? "full" : "release"));
! }
!
! private String version(String key) {
! return System.getProperty("java.version");
! }
!
! static String getMessage(String key, Object... args) {
! try {
! return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
! } catch (MissingResourceException e) {
! throw new InternalError("Missing message: " + key);
! }
! }
!
! private static class ResourceBundleHelper {
! static final ResourceBundle bundle;
!
! static {
! Locale locale = Locale.getDefault();
! try {
! bundle = ResourceBundle.getBundle("jdk.tools.jimage.resources.jimage", locale);
! } catch (MissingResourceException e) {
! throw new InternalError("Cannot find jimage resource bundle for locale " + locale);
! }
! }
}
}
--- 353,416 ----
print(reader, name);
}
}
}
! private void iterate(JImageAction jimageAction,
! ResourceAction resourceAction) throws IOException, BadArgs {
for (File file : options.jimages) {
if (!file.exists() || !file.isFile()) {
! throw taskHelper.newBadArgs("err.not.a.jimage", file.getName());
}
String path = file.getCanonicalPath();
BasicImageReader reader = BasicImageReader.open(path);
if (jimageAction != null) {
jimageAction.apply(file, reader);
}
if (resourceAction != null) {
! String[] entryNames = reader.getEntryNames();
for (String name : entryNames) {
+ if (!ImageResourcesTree.isTreeInfoResource(name)) {
ImageLocation location = reader.findLocation(name);
resourceAction.apply(reader, name, location);
}
}
}
}
+ }
private boolean run() throws IOException, BadArgs {
switch (options.task) {
case EXTRACT:
iterate(null, this::extract);
break;
case INFO:
iterate(this::info, null);
break;
case LIST:
iterate(this::listTitle, this::list);
break;
+ case RECREATE:
+ recreate();
+ break;
+ case SET:
+ iterate(this::set, null);
+ break;
case VERIFY:
iterate(this::title, this::verify);
break;
default:
! throw taskHelper.newBadArgs("err.invalid.task", options.task.name()).showUsage(true);
}
return true;
}
private PrintWriter log;
void setLog(PrintWriter out) {
log = out;
! taskHelper.setLog(log);
}
}
< prev index next >