docs: function addMethod(name, method, constructor, opts) {
          opts = opts || {};
  
          var metadata,
              isStatic = !!opts.isStatic,
              forcePublic = !!(opts.forcePublic || constructor[$class].isVanilla),
              isFinal,
              target,
              tmp,
              originalMethod,
              inherited;
  
          // Unwrap method if already wrapped
          if (method[$wrapped]) {
              method = method[$wrapped];
          }
  
          // Check if function is already being used by another class or within the same class
          if (method[$name]) {
              if (method[$name] !== name) {
                  tmp = method;
                  method = function () {
                      return tmp.apply(this, arguments);
                  };
                  obfuscateProperty(method, $name, name);
              }
          } else {
              obfuscateProperty(method, $name, name);
          }
  
          // If the initialize is inherited, copy the metadata
          if (!isStatic && name === 'initialize' && method.$inherited) {
              metadata = mixIn({}, constructor.$parent[$class].methods[name]);
              inherited = true;
              delete method.$inherited;
          } else if (opts.metadata) {
              metadata = opts.metadata;
              isFinal = metadata.isFinal;
          } else {
              // Grab function metadata and throw error if is not valid (it's invalid if the arguments are invalid)
              if (method[$wrapped]) {
                  throw new Error('Cannot grab metadata from wrapped method.');
              }
              metadata = functionMeta(method, name);
              if (!metadata) {
                  throw new Error((isStatic ? 'Static method' : 'Method') + ' "' + name + '" contains optional arguments before mandatory ones in class "' + constructor.prototype.$name + '".');
              }
  
              metadata.isFinal = isFinal = !!opts.isFinal;
  
              if (isStatic) {
                  if (constructor[$class].staticMethods[name]) {
                      metadata.allowed = constructor[$class].staticMethods[name].allowed;
                  }
              } else {
                  if (constructor[$class].methods[name]) {
                      metadata.allowed = constructor[$class].methods[name].allowed;
                  }
              }
          }
  
          // Force public if told so
          if (forcePublic) {
              forcePublicMetadata(metadata);
          }
  
          // Take care of $prefix if the method is initialize
          if (name === 'initialize' && method.$prefix != null) {
              if (method.$prefix === '_') {
                  metadata.isProtected = true;
              } else if (method.$prefix === '__') {
                  metadata.isPrivate = true;
              }
  
              delete method.$prefix;
          }
  
          // Check if it's a private method classified as final
          if (metadata.isPrivate && isFinal) {
              throw new Error('Private method "' + name + '" cannot be classified as final in class "' + constructor.prototype.$name + '".');
          }
  
          // Check if a property with the same name exists
          target = isStatic ? constructor[$class].staticProperties : constructor[$class].properties;
          if (isObject(target[name])) {
              throw new Error((isStatic ? 'Static method' : 'Method') + ' "' + name + '" is overwriting a ' + (isStatic ? 'static ' : '') + 'property with the same name in class "' + constructor.prototype.$name + '".');
          }
  
          target = isStatic ? constructor[$class].staticMethods : constructor[$class].methods;
  
          // Check if the method already exists
          if (isObject(target[name])) {
              if (target[name].forcedPublic) {
                  forcePublicMetadata(metadata);
              } else {
                  // Are we overriding a private method?
                  if (target[name].isPrivate && name !== 'initialize') {
                      throw new Error('Cannot override private ' + (isStatic ? 'static ' : '') + ' method "' + name + '" in class "' + constructor.prototype.$name + '".');
                  }
              }
  
              // Are we overriding a final method?
              if (target[name].isFinal) {
                  throw new Error('Cannot override final method "' + name + '" in class "' + constructor.prototype.$name + '".');
              }
              // Are they compatible?
              if (metadata.checkCompatibility && !isFunctionCompatible(metadata, target[name])) {
                  throw new Error((isStatic ? 'Static method' : 'Method') + ' "' + name + '(' + metadata.signature + ')" defined in abstract class "' + constructor.prototype.$name + '" overrides its ancestor but it is not compatible with its signature: "' + name + '(' + target[name].signature + ')".');
              }
          }
  
          target[name] = metadata;
  
          if (!isStatic) {
              constructor[$class].ownMembers[name] = true;
          }
  
          originalMethod = method;
          method = wrapMethod(name, method, constructor, isStatic);
  
          obfuscateProperty(method, $name, name);
          metadata.implementation = method;
  
          // Add it to the constructor or the prototype only if public
          if (metadata.isPublic || !hasDefineProperty) {
              target = isStatic ? constructor : constructor.prototype;
              target[name] = method;
          }
  
          // If the function is specified to be bound, add it to the binds
          if (originalMethod[$bound]) {
              if (!isStatic) {
                  insert(constructor[$class].binds, name);
              }
              delete originalMethod[$bound];
          }
  
          if (metadata.isProtected) {
              if (metadata.allowed) {
                  insert(metadata.allowed, constructor[$class].id);
              } else {
                  metadata.allowed = [constructor[$class].id];
              }
          } else if (metadata.isPrivate) {
              metadata.allowed = constructor[$class].id;
          }
      }