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
        )
);
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>