HTTPS SSH

API Design Choices

  1. To the extent possible, member variables should be final. Immutability is also desired, although
    in many cases performance dictates that only limited immutability is reasonable. An example of this
    is that an image's dimensions will not change, the data reference will not change, but the content
    of the data is still mutable.
  2. Data and functionality should be separated as cleanly and reasonably as possible, although it is
    not required to go so far as POJOs and static functions. This tends to simplify the type hierarchy
    and encourages functional interfaces for complex logic that can then be incorporated into more
    conventional OO-types by using has-a relationships instead of is-a.
  3. Colors and pixels would ideally be value types but because Java does not yet support them,
    compromises are made in the API. Namely, these value instances often are included as a result
    argument for a function instead of the return type. Alternatively, a reused instance is used
    internally by some longer-lived processor, effectively using the instances in the flyweight pattern.
  4. Arguments are validated when the values' correctness is not immediately checked or tested by
    another function or implicit aspect of Java. Implicit validation can occur when an argument is
    required to be non-null, and a side-effect free member is accessed; it can occur with array access
    or creation as well where the index is validated. The important thing is that all arguments must be
    explicitly or implicitly validated before any side effects of the function can occur.
  5. Argument validation for internal and low-level classes can be eschewed for performance reasons if
    unit tests assure they are invoked properly by higher-level APIs that do validate arguments. This is
    primarily for performance reasons and to avoid redundant checks and assertions.
  6. For complex type creation, the builder pattern is preferred so that a fluent API can be used.
    Internal, or very low-level, types do not require the builder pattern unless it happens to greatly
    improve code readability.
  7. Utility functions that operate on objects should be grouped into related utility classes that are
    final, have a private constructor, and only expose static methods. Since they are utilities, there
    is no need to develop an interface and type hierarchy around them. This is in contrast to, as an
    example, the design of the I/O package that does provide a set of interfaces that different image
    formats implement.
  8. Naming of the utility functions should be based on the package name, or primary class it operates
    on. A goal should be to keep the name short, possibly pluralized, and avoid a suffix like Util.
  9. Object arguments should be assumed not nullable unless appropriately annotated or documented,
    preferably with @Null defined in Arguments.
  10. For simplicity in validation, it can be assumed that enum arguments are never null.
  11. NIO buffer allocation can create unpredictable performance problems if there are too many
    implementations loaded into the JVM, eliminating virtual function optimizations that might otherwise
    be performed. To that end, the BufferFactory class is defined to allow some flexibility in how
    buffers are allocated. Data.getBufferFactory() should be used for all buffer creations in any code
    within imaJe, and ideally that extends it. This helps preserve the consistent use of a given type of
    buffer, e.g. direct, native byte ordering, array backed, or using a third-party library to have more
    optimized allocation.
  12. The mutable position and limit states of Buffers is awkward. To maintain consistency with the NIO
    channel APIs, all get and set operations that read/write to buffer arguments will respect and modify
    the position and limit as appropriate. However, classes such as the XBufferData implementations that
    wrap the contents of the Buffer will operate on the entire 0 to capacity range of elements. To
    preserve this guarantee, they make a duplicate() on creation, and expose duplicates with their
    getSource() methods. If a DataBuffer must wrap a subrange of an NIO buffer, then the slice()
    function should be used to create a smaller Buffer that maps to that range while exposing the data
    as expected by the XBufferData implementations.
  13. The I/O operations for reading/writing images offers a single type of operation: riting and
    reading to a SeekableByteChannel (basically a FileChannel), which tie that part of the interface to
    only reading or writing to a file system. It is outside the scope of this project to support
    networked image storage. Internally, readers and writers allocate a fixed length ByteBuffer with
    Data.getBufferFactory() and use the utility methods in IO to stream content effectively.

Code Style

  1. Use spaces instead of tabs. Indentation width is set to 2 spaces.
  2. IntelliJ's autoformatting and organizing is required for final code release.