limansky / beanpuree

Middle layer between JavaBeans and shapeless
Apache License 2.0
46 stars 10 forks source link

Can't find the required implicits #9

Open aghasemi opened 5 years ago

aghasemi commented 5 years ago

Hi, Intellij (and Maven) cannot, somehow, find the required implicits when I define a BeanConverter. I have imported me.limansky.beanpuree._. Any idea how I can debug this?

limansky commented 5 years ago

You can try to start your investigation with check if LabelledBeanGeneric instance can be found for your class. And if LabelledGeneric is defined for your case class.

aghasemi commented 5 years ago

Thanks. Seems the issue is with the bean (LabelledBeanGeneric). Is there any criteria bean classes should meet? In my case, the class implements Serializable and fields are protected (instead of private), yet everything else is a typical bean.

aghasemi commented 5 years ago

Two other suspicions:

  1. Does it support multiple fields with the same type in a bean/case class?
  2. Does it (or JavaTypeMapper) support nesting?
aghasemi commented 5 years ago

Further tracking down, it seems the problem is the Align class. Any criteria here?

limansky commented 5 years ago

Hm... As I remember Align is using only in StrictBeanConverter. Anyway there no limitations in Align or AlignByKey itself.

If I were you I would try to comment out pairs of getters and setters one by one to find the problem one.

vtitov commented 5 years ago

Similar issue. I'm using "me.limansky" %% "beanpuree" % "0.4" And for the following test:

package example

import org.scalatest._

import me.limansky.beanpuree._
import me.limansky.beanpuree.BeanConverter
import me.limansky.beanpuree.BeanConverter._

import scala.beans.BeanProperty

class Foo(@BeanProperty var a:Int, @BeanProperty var b: String) {override def toString = s"$a:$b"}
case class Bar(a: Int, b: String) {override def toString = s"$a:$b"}

class ConvTest extends WordSpecLike with Matchers {
  "Suite" should {
    "compare" in {
      val p = Bar(1,"a")
      val b = new Foo(1,"a")
      p.a shouldEqual b.getA
      p.b shouldEqual b.getB
    }
    "convert" in {
      val p = Bar(1,"a")
      val conv = BeanConverter[Foo, Bar]
      val b = conv.productToBean(p)
      p.a shouldEqual b.getA
      p.b shouldEqual b.getB
    }
  }
}

produces error:

beanpuree\CmdParserTest.scala:25:31: could not find implicit value for parameter sbc: me.limansky.beanpuree.BeanConverter[example.Foo,example.Bar]
[error]       val conv = BeanConverter[Foo, Bar]
[error]                               ^
[error] one error found
[error] (Test / compileIncremental) Compilation failed

When I comment out convert, Suite gets passed. What is wrong?

limansky commented 5 years ago

Hi @vtitov

I've never tried to use beans created as a Scala classes with BeanProperty annotations. It looks like there is some difference between these classes and real Java beans. I'll investigate that. Thanks for isolating problem.

limansky commented 5 years ago

I've found that's wrong with your class: it doesn't have default constructor. Thus, it's not a JavaBean by definition: https://en.wikipedia.org/wiki/JavaBeans

They are serializable, have a zero-argument constructor, and allow access to properties using getter and setter methods.

If you change it:

scala> class Foo(@BeanProperty var a:Int, @BeanProperty var b: String) {override def toString = s"$a:$b";  def this() = this(0, "") }
defined class Foo

scala> val gen = BeanGeneric[Foo]
gen: me.limansky.beanpuree.BeanGeneric[Foo]{type Repr = Int :: String :: shapeless.HNil} = $anon$1@8ff9b6f

scala> gen.from(1 :: "aaa" :: HNil)
res2: Foo = 1:aaa

The reason of this behavior is that BeanGeneric (or LabelledBeanGeneric) uses the default constructor to create object from HList. In case of your class the generated code will be like:

val gen = new BeanGeneric[Foo] {
  override type Repr = Int :: String :: HNil
  override def to(foo: Foo): Repr = foo.getA :: foo.getB :: HNil
  override def from(repr: Repr) = repr match {
    case a :: b :: HNil =>
     val foo = new Foo()
     foo.setA(a)
     foo.setB(b)
     foo
  }
}
limansky commented 5 years ago

@aghasemi is it the same issue you have?

limansky commented 5 years ago

@vtitov Is this kind of classes are really used in your projects? I think it's possible to support it, if it really required.

vtitov commented 5 years ago

Mike,

No, it were not real classes. I faced similar problem while attempting to use beanpuree from pure Java. I try to use scala adapter object as a workaround.

limansky commented 5 years ago

@vtitov Could you provide problem Java class?

vtitov commented 5 years ago

@limansky, the issue was somewhat different:

...ConvTest.scala:15:46: could not find implicit value for parameter sbc: > me.limansky.beanpuree.BeanConverter[B,P] [error] @BeanProperty val converter = BeanConverter[B, P] [error] ^ [error] one error found

scala helper


class JavaBeanConverterHelper[B,P] {
  @BeanProperty val converter = BeanConverter[B, P]
}

java converter

package example;

import me.limansky.beanpuree.*;

public class JavaBeanConverter<B, P> {
    private BeanConverter<B, P> converter = new JavaBeanConverterHelper<B, P>().getConverter();

    public B productToBean(P p) { return converter.productToBean(p); }
    public P beanToProduct(B b) { return converter.beanToProduct(b); }
}

testcase

    "convert-java" in {
      val p = Bar(1,"a")
      val conv = new JavaBeanConverter[Foo, Bar]
      val b = conv.productToBean(p)
      p.a shouldEqual b.getA
      p.b shouldEqual b.getB
    }