GithubHelp home page GithubHelp logo

Comments (33)

keith-anders avatar keith-anders commented on May 22, 2024 1

I forgot about a commitment I have tonight. I'm almost done with the change, though. Tomorrow night for sure.

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

Hi, nice idea.
We accept PR ;-)

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

If I understand correctly, OP wants it to work something like this (untested):

class Aspect : OnMethodBoundaryAspect {
  public override void OnEntry(MethodExecutionArgs args) {
    args.FlowBehavior = FlowBehavior.Return;
    args.ReturnValue = "passed";
  }
}

class TestClass {
  [Aspect]public static string Method() => throw new Exception("failed");
}

class Tests {
  [Fact] public void Test() {
    Assert.Equal("passed", TestClass.Method());
  }
}

It would only work in OnEntry and should probably trigger the same behavior as FlowBehavior.Continue when used after OnException. This is not difficult to do.

However, there's an edge case to consider. Consider the case where multiple aspects, each with an OnEntry handler, all apply to the same method. PostSharp's flow behavior here is a little subtle. If an aspect's OnEntry sets flow behavior to Return, then it will indeed return before running any further aspects, but first it will trigger the OnExit of any aspects which already ran their OnEntrys. See http://doc.postsharp.net/t_postsharp_aspects_flowbehavior for the docs.

The idea of PostSharp's implementation is to mimic the behavior of an interceptor: if you don't allow flow to continue past your OnEntry, then deeper layers (including the unwoven method) will not be executed but any aspects at a higher layer than you (whose entry happened before yours) will still get their OnExit. The idea is to allow any aspects which already began their executions to clean up any resources they claimed.

I assume, based on past interactions, that @Ralf1108 and @marcells would want the behavior of MBA.Fody to mimic PostSharp as much as possible, but one of them should probably confirm on this subject. Would also be nice to hear from OP about this.

I can probably work on this over the weekend, but I wanted to point all this out in case someone beats me to it.

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

You are correct. Mimicing the behaviour of post sharp users have it easier to transition to MBA and learned stuff from post sharp can be reused

from methodboundaryaspect.fody.

KleaTech avatar KleaTech commented on May 22, 2024

This is what I'd like to achive. I'm not sure if this is the correct way to do it.

public class CacheThisMethod : OnMethodBoundaryAspect
{
        public override void OnEntry(MethodExecutionArgs args)
        {
           if (isCached( args.Method.name)
               {
                args.MethodExecutionTag =  getReturnValue(args.Method.name)
                OnExit(args);
               }
           else
               {
                //continue
               }
        }
        public override void OnExit(MethodExecutionArgs args)
        {
            args.ReturnValue = args.MethodExecutionTag;
            args.FlowBehavior = FlowBehavior.Return;
        }
}

Note that this is not the complete code, it just shows the basic idea.

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

Ok. I'm pretty much done with the Return flow behavior when set from OnEntry. However, I don't know what to do for the OnException case. That is, if I have two aspects handling OnException, and the first one changes FlowBehavior to Return (or Continue for that matter), should the second one get its OnException handler called or its OnExit or neither? I don't know what PostSharp's behavior is in this case. (and note that MBA's OnExit is analogous to PostSharp's OnSuccess, not PostSharp's OnExit) What should MBA do? As soon as I have an idea what to do with that, I can probably finish this enhancement. The latest version will only check FlowBehavior after the last aspect implementing OnException is called, not in between each one as PostSharp seems to do.

I also just thought of another issue on this feature. Consider the following:

class Aspect : OnMethodBoundaryAspect {
  public override void OnEntry(MethodExecutionArgs args) {
    args.FlowBehavior = FlowBehavior.Return;
    args.ReturnValue = 42;
  }
}

class Program {
  private static int _field = 0;

  [Aspect]
  public static ref int GetInt() => ref m_field;
}

As is, the method will get transformed to this:

public static ref int GetInt() {
  var aspect = new Aspect();
  var args = new MethodExecutionArgs();
  // various setup
  aspect.OnEntry(args);
  if (args.FlowBehavior == FlowBehavior.Return) {
    int returnValue = args.ReturnValue;
    return ref returnValue;
  }
  return ref m_field;
}

But that's illegal: you cannot return a byref variable that refers to a local inside your own method. I don't know whether the JITter would throw an intelligent exception or just crash, but it won't be desired behavior either way. Perhaps for ref return types it should not check for FlowBehavior after OnEntry. Or perhaps it should be weaved to this instead:

public static ref int GetInt() {
  var aspect = new Aspect();
  var args = new MethodExecutionArgs();
  // various setup
  aspect.OnEntry(args);
  if (args.FlowBehavior == FlowBehavior.Return) {
    throw new InvalidOperationException("FlowBehavior.Return is not supported in OnEntry when return type is ByReference.");
  }
  return ref m_field;
}

Again, I have no idea how PostSharp handles it. I'll hold on making any more progress until I hear back on these issues.

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

If there is no other recommendation from @KleaTech regarding the expected behavior I think we should keep it for now like you implemented it. Maybe later during usage of this feature there will be more insights what is the correct behavior with specific use cases.

Regarding the "ref return".
For now we should throw just an NotSupportedException. In my opinion the only scenarios where the "ref return" will be used are for low level high performance code and because of this there wont be any aspect woven into. Also here we wait for a use case before putting more effort in it -> YAGNI ;-)

from methodboundaryaspect.fody.

KleaTech avatar KleaTech commented on May 22, 2024

I don't have enough experience to tell what is the expected behaviors in the special cases. But since you asked, here's my opinion. I think the "return ref" case is not that important, so the NotSuppertedException is fine for now.
Regarding the OnException case, I don't know how PostScript handles it, but I think this would be a sufficient behavior: if you set FlowBehavior to Return in any hook (OnEntry, OnException or OnExit), the method should return immediately after that hook. So no other hook would be triggered after that. If there are more aspects, than their order will define which one will run and which won't. If there is an aspect with FlowBehavior set to Return in one of it's hooks, the aspects after that won't run.
Also I can't see a reason not to allow FlowBehavior.Return in the OnExit hook, so please make it possible to use it there. So my example would work. Below is a more real example which - according to a StackOverflow answer - should work with PostScript.

public sealed class CacheAttribute : OnMethodBoundaryAspect
    {
        public static DictionaryCache Cache { get; set; } = new DictionaryCache();
        public override void OnEntry(MethodExecutionArgs args)
        {
            var method = args.Method;
            var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);
            if (Cache.Contains(cacheKey))
            {
                args.MethodExecutionTag = Cache.Retrieve<object>(cacheKey);
                OnExit(args);
            }
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            var method = args.Method;
            var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);
            if (Cache.Contains(cacheKey))
            {
                args.ReturnValue = args.MethodExecutionTag;
                args.FlowBehavior = FlowBehavior.Return;
            }
            else
                Cache.Store(cacheKey, args.ReturnValue);
        }

        public override void OnException(MethodExecutionArgs args)
        {
        }
    }

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

To make OnExit respect FlowBehavior.Return would definitely contradict PostSharp's behavior. @Ralf1108 what do you say?

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

@KleaTech
would this implementation not simplify your use case?
If method is called and a cached value exists -> return
If method is called and a no cached value exists -> method is invoked and return value is added to cache. Next method call will use the cached value.

public sealed class CacheAttribute : OnMethodBoundaryAspect
{
	public static DictionaryCache Cache { get; set; } = new DictionaryCache();
	public override void OnEntry(MethodExecutionArgs args)
	{
		var method = args.Method;
		var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);
		if (Cache.Contains(cacheKey))
		{
			args.ReturnValue = Cache.Retrieve<object>(cacheKey);
			args.FlowBehavior = FlowBehavior.Return;
		}
	}

public override void OnExit(MethodExecutionArgs args)
	{
		var method = args.Method;
		var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);

                Cache.Store(cacheKey, args.ReturnValue);			
	}
}

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

Thinking about the whole feature and discussing it with a colleage:
What @KleaTech really want is a "MethodInterceptionAspect". This also exists in PostSharp.
Our "MethodBoundaryAspect" is more specialized for adding code which is executed everytime.

So for implementing the "return flow behavior" I think the OnEntry and OnExit on all aspects should be callled to not introduce strange and difficult to debug side effects.
If a aspect mind to dont do anything if a return value was set then it can skip execution explicitly. But the default expectation from the aspect is that all OnXXX methods are called appropriately.

That would mean for the implementation:

  • check return flow only after OnXXX methods of all aspects were called
  • after calling one OnXXX method we have to transfer the "ReturnValue" and "FlowBehavior" value from the last used MethodExecutionArgs to the current one, so the next aspect can use this information.

To 100% match @KleaTech requirements we should implement a MethodInterceptionAspect. Hopefully there could be many code from this aspect be reused.
With this you can control if and how often a method should be called, either for using cached values or implement a retry behavior.

Any thoughts? :-)

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

So, @Ralf1108 , are you saying that OnExit should not be called even if FlowBehavior.Return is used in OnEntry, despite that being the way PostSharp does it? For example, this...

[Aspect1, Aspect2, Aspect3]public static string GetMessage() {
  return "Hello, World!";
}

gets weaved to this in PostSharp...

public static string GetMessage() {
  var aspect1 = new Aspect1();
  var aspect2 = new Aspect2();
  var aspect3 = new Aspect3();
  var args = new MethodExecutionArgs();
  // various setup
  aspect1.OnEntry(args);
  var tag1 = args.MethodExecutionTag;
  if (args.FlowBehavior == FlowBehavior.Return)
    return (string)args.ReturnValue;
  aspect2.OnEntry(args);
  var tag2 = args.MethodExecutionTag;
  if (args.FlowBehavior == FlowBehavior.Return) {
    args.MethodExecutionTag = tag1;
    aspect1.OnExit(args);
    return (string)args.ReturnValue;
  }
  aspect3.OnEntry(args);
  var tag3 = args.MethodExecutionTag;
  if (args.FlowBehavior == FlowBehavior.Return) {
    args.MethodExecutionTag = tag2;
    aspect2.OnExit(args);
    args.MethodExecutionTag = tag1;
    aspect1.OnExit(args);
  }
  string returnValue = null;

  try {
    returnValue = "Hello, World!";
  }
  catch (Exception ex) {
    args.Exception = ex;
    args.MethodExecutionTag = tag3;
    aspect3.OnException(args);
    if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
      args.MethodExecutionTag = tag2;
      aspect2.OnExit(args);
      args.MethodExecutionTag = tag1;
      aspect1.OnExit(args);
      return (string)args.ReturnValue;
    }
    args.MethodExecutionTag = tag2;
    aspect2.OnException(args);
    if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
      args.MethodExecutionTag = tag1;
      aspect1.OnExit(args);
      return (string)args.ReturnValue;
    }
    args.MethodExecutionTag = tag1;
    aspect1.OnException(args);
    if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
      return (string)args.ReturnValue;
    }
  }
  args.ReturnValue = returnValue;
  args.MethodExecutionTag = tag3;
  aspect3.OnExit();
  args.MethodExecutionTag = tag2;
  aspect2.OnExit();
  args.MethodExecutionTag = tag1;
  aspect1.OnExit();
  return (string)args.ReturnValue;
}

Or, at least, that's how I understand it, according to the docs on return. But you're saying that in MBA it should just be the following?

public static string GetMessage() {
  var aspect1 = new Aspect1();
  var aspect2 = new Aspect2();
  var aspect3 = new Aspect3();
  var args = new MethodExecutionArgs();
  // various setup
  aspect1.OnEntry(args);
  var tag1 = args.MethodExecutionTag;
  aspect2.OnEntry(args);
  var tag2 = args.MethodExecutionTag;
  aspect3.OnEntry(args);
  var tag3 = args.MethodExecutionTag;
  if (args.FlowBehavior == FlowBehavior.Return) {
    return (string)args.ReturnValue;
  }
  string returnValue = null;

  try {
    returnValue = "Hello, World!";
  }
  catch (Exception ex) {
    args.Exception = ex;
    args.MethodExecutionTag = tag3;
    aspect3.OnException(args);
    args.MethodExecutionTag = tag2;
    aspect2.OnException(args);
    args.MethodExecutionTag = tag1;
    aspect1.OnException(args);
    if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
      return (string)args.ReturnValue;
    }
  }
  args.ReturnValue = returnValue;
  args.MethodExecutionTag = tag3;
  aspect3.OnExit();
  args.MethodExecutionTag = tag2;
  aspect2.OnExit();
  args.MethodExecutionTag = tag1;
  aspect1.OnExit();
  return (string)args.ReturnValue;
}

Is that right?

I don't particularly mind either way, and implementing the interception aspect sounds fun. I just want to make sure I'm clear about what I'm trying to build.

from methodboundaryaspect.fody.

KleaTech avatar KleaTech commented on May 22, 2024

@Ralf1108 Your example should satisfy my use case. It's worth to think about though, what would happen if we would have a caching attribute and a retry attribute together. The MethodInterceptionAspect is a good idea, so there will be two different approach to interact with a method.

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

@Ralf1108 What do you think? Should it run every OnEntry and OnExit even though that's not how PostSharp does it?

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

Hi,

maybe I wrote it too complicated :-)
Of course I want that for each OnEntry there is a OnExit or OnException call. Sorry for the irritation.
The way it works should be as intuitive as it can be and so far post sharp has the most experience in that area. We should stick to them.

Regarding last comment from @KleaTech ( #44 (comment))

The MethodInterceptionAspect is are more general type of aspect as you can say something like "Execute the original now" and even repeat this. Where should this logic be in the MethodBoundaryAspect? Should one aspect be able to call in its OnEntry method the "Execute the original now" ? In my opinion this makes no sense.
With the MethodInterceptionAspect you can always mimic the MethodBoundaryAspect, just use a try-catch around the "Execute()" method.
If we really want this this should be an on AspectBaseClass... like in PostSharp ;-)

from methodboundaryaspect.fody.

martijnvdm avatar martijnvdm commented on May 22, 2024

Hi,

So is there any plan to implement "FlowBehavior.Return"? I would really like this feature as well.

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

@keith-anders
do you plan to make an PR?

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

I did, but I got sidetracked onto other things. If someone else wants to do it, they will probably get theirs in before me.

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

maybe you can make your current progress public via github so someone can continue from this?

from methodboundaryaspect.fody.

rolandso avatar rolandso commented on May 22, 2024

Any updates on this? I need to stop my method execution or return it inside OnEntry.. In PostSharp there should be FlowBehaviour.Return for this problem.

from methodboundaryaspect.fody.

EtienneT avatar EtienneT commented on May 22, 2024

@keith-anders any update on the pull-request? Maybe make it public so that someone else can continue? This is exactly the feature I need.

Thanks,

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

Sorry about that, everyone. I'll endeavor to get something in within 48 hours from now.

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

I just submitted #61 that should implement this. Here is what I implemented:

After each OnEntry is run: the code checks for FlowBehavior.Return. If found, then all previous aspects (whether or not they had an OnEntry to run) will get their OnExit called in reverse order. Setting the ReturnValue somewhere either in the aborting OnEntry or in one of the OnExits is important.

After each OnException is run: the code checks for FlowBehavior.Return or FlowBehavior.Continue which are treated identically. In a synchronous method, one of these would cause the remaining aspects to get their OnExits run instead of their OnExceptions. In an async method, the aspect that set the flow behavior will be the last aspect to run: no others will run after. It must also set the ReturnValue which will be set as the result of the task. The remaining aspects that might have had their OnExceptions run will instead not be called.

No behavior is attached to OnExit since at that point it's too late to change the means of exiting the function.

Once FlowBehavior.Return is set by one aspect, from either OnEntry or OnException, it is never checked again--the Return behavior persists for the duration of that method call.

I did not implement MethodInterceptionAspect; that is a much larger project for another day.

I hope this satisfies everyone's needs, if not as conveniently as you might have hoped.

from methodboundaryaspect.fody.

rolandso avatar rolandso commented on May 22, 2024

@Ralf1108 Can you look over the PR?

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

PR looks good, but we have to ensure that we mimic PostSharp behavior for FlowBehavior. Or else migrators to MBA could get confused.
@keith-anders Does this match the PostSharp behavior?

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

Here are the behaviors I implemented in a more line-item format:

  1. OnEntry sets FlowBehavior.Return => use args.ReturnValue, run the previous aspects' OnExit in reverse order, do not execute the unwoven method.
  2. Non-state machine methods: OnException sets FlowBehavior.Return or FlowBehavior.Continue => use args.ReturnValue, run the previous aspects' OnExit in reverse order, do not rethrow the exception.
  3. Yield return: untested. I believe MBA.Fody does not officially support this yet.
  4. Async: OnException sets FlowBehavior.Return or FlowBehavior.Continue =>
    a. Use args.ReturnValue to set the result of the task.
    b. Do not run any of the OnExits or any remaining OnExceptions.

Item 1, 2, and 4a are exactly as in PostSharp, subject to the caveat that OnExit in MBA.Fody is treated like OnSuccess in PostSharp: it is only run when the method exits normally, not in the event of an exception. That has been a difference between the two projects for years.

Item 4b is a difference from PostSharp. The issue is that PostSharp runs its OnExit and OnSuccess handlers from the state machine when the task is done, not in the original method as MBA.Fody does. I could have added the OnExit calls to the exception handler, but to do so now would mean that OnExit would be called twice but only in the event of an exception, which I think is more confusing than what I implemented. I'm not opposed to the happy path OnExit call being moved inside the state machine, but in my opinion that is out of scope for this issue. Adding the OnExit handlers to the async OnException should wait until that happens so that the OnExits don't get run twice.

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

Thanks for the clarification!
Regarding the different behavior of MBA.Fody and PostSharp. Should we fix this (in a new issue/PR) ?

So if the remaining behavior for the synchronous calls works as in PostSharp then i will merge the PR. For upcoming problems with async methods we wait for new issues.

@KleaTech please confirm the implemented FlowBehavior with latest version so we can close this issue.

Thx Keith for your hard work 👍

from methodboundaryaspect.fody.

keith-anders avatar keith-anders commented on May 22, 2024

I think it should probably be fixed, yes. Introduce a new OnSuccess method that takes the properties of the current OnExit, and rewrite OnExit to act more like PostSharp's.

However, as that is a breaking change to behavior, it's a major version increment. It might be worthwhile to put together a number of these changes and make them all in a single major version upgrade. Just an idea.

from methodboundaryaspect.fody.

KleaTech avatar KleaTech commented on May 22, 2024

I tested my usecase with the code below. It works as expected. I'm not sure about how this compares to the PostSharp implementation, I haven't used that.

Thank You for your work! You can close the issue.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using MethodBoundaryAspect.Fody.Attributes;

namespace Cachetest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(ComplexMethod());
            Console.WriteLine(ComplexMethod());
            Console.ReadKey();
        }

        [Cache]
        static int ComplexMethod()
        {
            Thread.Sleep(5000);
            return 42;
        }
    }

    class CacheAttribute : OnMethodBoundaryAspect
    {
        public static Dictionary<string, object> Cache { get; } = new Dictionary<string, object>();

        public override void OnEntry(MethodExecutionArgs args)
        {
            var method = args.Method;
            var cacheKey = method.DeclaringType.FullName + method.Name + '_' + string.Join("_", args.Arguments);
            var value = Cache.FirstOrDefault(e => e.Key == cacheKey).Value;
            if (value == null) return;
            args.ReturnValue = value;
            args.FlowBehavior = FlowBehavior.Return;
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            var method = args.Method;
            var cacheKey = method.DeclaringType.FullName + method.Name + '_' + string.Join("_", args.Arguments);
            Cache[cacheKey] = args.ReturnValue;
        }
    }
}

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

@KleaTech thx for your feedback and the example. I will then close the issue

from methodboundaryaspect.fody.

Rahul9179 avatar Rahul9179 commented on May 22, 2024

Hi Team,
I have implemented KingAOP in our projects but we are not able to invoke our cache attribute in DataContext level. Ca you please suggest me hw can we invoked or anything is missing for this.

Thanks in advance.

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

@Rahul9179
please create a new issue for your request and also a small demo code so we can understand what exactly is the problem or your expectations. Your two sentences are to general... something doesn't work...
If you can provide more specifics, best would be a test case in code, then investigation would be easier and also faster

from methodboundaryaspect.fody.

Ralf1108 avatar Ralf1108 commented on May 22, 2024

@Rahul9179
also you wrote that you use KingAOP which is a totally different project than our MethodBoundaryAspect.
If your problems is connected to the usage of KingAOP then you would get better answers when creating an issue on their repository.

from methodboundaryaspect.fody.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.