Use lambda to provide stack based context (for example, the path of a file operation)
I have a subtle way (which may have been implemented and discussed) to do the same in groovy (from groovy documentation here). I try to improve it, if possible:
ant.sequential { echo("inside sequential") def myDir = "target/AntTest/" mkdir(dir: myDir) copy(todir: myDir) { fileset(dir: "src/test") { include(name: "**/*.groovy") } } echo("done") }
This example is very good, but I didn't do an ant based program: I want to do some smooth APIs to operate files and directories (let's call it filemanipulator, my name is terrible:)
Basically, this will be:
new DefaultFileManipulator(Paths.get("root")).with(() -> { newFile("file1"); // create file root/file1 newFile("file2"); // create file root/file2 cd("directory1",() -> { // create directory root/directory1 newFile("file1"); // create file root/directory1/file1 cd("directory1",() -> { // create direcotry root/directory1/directory1 newFile("file1"); // create file root/directory1/directory1/file1 }); newFile("file2"); // create file root/directory1/file2 }); });
Use the following interface:
interface FileManipulator { FileManipulator with(LambdaFileManipulator m); FileManipulator cd(String path,LambdaFileManipulator m); Path newFile(String path) throws IOException; } @FunctionalInterface interface LambdaFileManipulator extends FileManipulator { void execute() throws IOException; default FileManipulator with(LambdaFileManipulator m) { return FileManipulatorStack.manipulatorFor(this).with(m); } // for each non default method of FileManipulator,the same call to FileManipulatorStack.manipulatorFor(this). }
The defaultfilemanipulator simply implements filemanipulator and uses the abstractfilemanipulator defined below
Because I want the path to be relative, I need to maintain the lambda context in some way (I don't want to mess up the current working directory): I use the stack of this operation:
// visibility package,because that's technical stuff! class FileManipulatorStack { private static final Map<LambdaFileManipulator,ArrayDeque<LambdaFileManipulatorDelegator>> stacks = new ConcurrentHashMap<>(); static LambdaFileManipulatorDelegator manipulatorFor(final LambdaFileManipulator delegatee) { final ArrayDeque<LambdaFileManipulatorDelegator> stack = stacks.get(delegatee); if (null == stack) { throw new IllegalStateException("state is empty for [" + delegatee + "]"); } return stack.getLast(); } static void delegate(final Path scopedPath,final LambdaFileManipulator delegatee) { final LambdaFileManipulatorDelegator handler = new LambdaFileManipulatorDelegator(scopedPath); final ArrayDeque<LambdaFileManipulatorDelegator> stack = stacks.computeIfAbsent(delegatee,key -> new ArrayDeque<>()); stack.addLast(handler); try { delegatee.execute(); } catch (final Exception e) { throw new DelegatedFileCreatorHandlerUndeclaredException(e); } finally { final LambdaFileManipulatorDelegator ss = stack.removeLast(); if (ss != handler) { throw new IllegalStateException("invalid stack"); } if (stack.isEmpty()) { stacks.remove(delegatee); } } } static class LambdaFileManipulatorDelegator extends AbstractFileManipulator { ... constructor ... } } abstract class AbstractFileManipulator implements FileManipulator { private final Path root; public AbstractFileManipulator(Path root) { this.root = requireNonNull(root,"root"); } public final FileManipulator with(LambdaFileManipulator m) { FileManipulatorStack.delegate(root,m); return this; } public final FileManipulator cd(String path,LambdaFileManipulator m) { FileManipulatorStack.delegate(root.resolve(path),m); return this; } public final Path newFile(String path) { Path p = root.resolve(path); Files.createFile(p); return p; } }
For stackoverflow rule, my question may not be "good", but here is:
How can I improve this without adding filemanipulator as a parameter to lambda (I'll use consumer instead)?
Is there a problem using lambda as the key to the map? (in this comment, it says that lambda will be dynamically converted to an instance of lambdafilemanipulator, and then I may not need stack mapping)
Did I miss some features of Java 8 that allow me to use lambda as an implementation method of a class?
Editor: I answered my own question... It doesn't work because lambda won't / will never know it's an implementation of filemanipulator Therefore, it cannot call those methods It may use static methods (and some context), but I think it's worse than having a parameter
Solution
I didn't see how it looks like the example shown, calling newfile () and CD () without the object in front of them.
They are not static functions, and closed classes have no reason to implement them
I propose something that does not use Lambdas but can run very close to your example
enum FileActionType { CD,NEWFIlE; } class FileAction { final public String name; final public FileActionType type; final public FileAction actions[]; public FileAction(String name,FileActionType type,FileAction[] actions) { this.name = name; this.type = type; this.actions = actions; } }
Achieve static functions:
public static void execute(File f,FileAction... actions) { f.mkdirs(); for (int i = 0; i < actions.length; i++) { FileAction action = actions[i]; switch (action.type) { case CD: execute(new File(f,action.name),action.actions); break; case NEWFIlE: try { Files.createFile(new File(f,action.name).toPath()); } catch (IOException ex) { } break; } } } public static FileAction cd(String name,FileAction... actions) { return new FileAction(name,FileActionType.CD,actions); } public static FileAction newFile(String name) { return new FileAction(name,FileActionType.NEWFIlE,null); }
Use this:
execute( new File("/tmp/test"),newFile("file1"),// create file root/file1 newFile("file2"),// create file root/file2 cd( "directory1",// create file root/directory1/file1 cd("directory1",// create direcotry root/directory1/directory1 newFile("file1") // create file root/directory1/directory1/file1 ),newFile("file2") // create file root/directory1/file2 ) );