Add a unified and optionally more constrained API for expressing filters on columns

This is a re-opened version of:
https://github.com/Parquet/parquet-mr/pull/412

The idea behind this pull request is to add a way to express filters on columns using DSL that allows parquet visibility into what is being filtered and how. This visibility will allow us to make optimizations at read time, the biggest one being filtering entire row groups or pages of records without even reading them based on the statistics / metadata that is stored along with each row group or page.

Included in this api are interfaces for user defined predicates, which must operate at the value level by may opt in to operating at the row group / page level as well. This should make this new API a superset of the `parquet.filter` package. This new api will need to be reconciled with the column filters currently in the `parquet.filter` package, but I wanted to get feedback on this first.

A limitation in both this api and the old one is that you can't do cross-column filters, eg: columX > columnY.
Yeah I thought about it.
I'm pretty certain that for Object.equals() I want the kind of equality here.

Adding logical equality could be useful, but here's the problems I could see with it:
- Should it be logical equality so and(x, y) is equal to and(y, x)? They're logically equal, but the short circuit in different orders. A common pattern is x != null && x.foo(). If you change the short circuit order you have a real impact on the correctness of your expression.
- To implement logical equality we probably need to normalize an expression into CDNF or CCNF and then use the Object.equals() method as it currently is on those two expressions
- This would probably only be used in testing
- I think we should add this as needed, because I think we want Object.equals() to be physical equality, and logical equality should get it's own method.